Skip to content

Commit 3eef26e

Browse files
committed
feat: allow sidebar to fully collapse via drag
- Change #sidebar's data-minsize from 30 to 0 so the Resizer's collapsible drag path (newSize < 10 → hide) fires when the user drags the right-edge handle to the left edge. The CCB sidebar-toggle button remains as the way to bring it back, so nothing is unreachable. - Add shared test/spec/DragTestUtils.js for programmatic drag input across the harness's browser engines, and use it to replace the data-minsize attribute assertion with a real drag that verifies the full-collapse behavior and that the CCB toggle is still visible.
1 parent b7a997b commit 3eef26e

4 files changed

Lines changed: 137 additions & 27 deletions

File tree

src/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@
905905
<div class="main-view forced-hidden">
906906
<div id="notificationUIDefaultAnchor" href="#">
907907
</div>
908-
<div id="sidebar" class="sidebar panel quiet-scrollbars horz-resizable right-resizer collapsible" data-minsize="30" data-maxsize="80%" data-forceleft=".content">
908+
<div id="sidebar" class="sidebar panel quiet-scrollbars horz-resizable right-resizer collapsible" data-minsize="0" data-maxsize="80%" data-forceleft=".content">
909909
<div id="mainNavBar">
910910
<div id="mainNavBarLeft">
911911
<div id="newProject" class="new-project-btn btn-alt-quiet"></div>

test/control-bar-tests-todo.md

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,6 @@ Keep this file updated as we add coverage; remove lines as suites land.
1111

1212
---
1313

14-
## 1. Layout / DOM structure
15-
16-
- [ ] `#centralControlBar` exists at boot, positioned between sidebar and
17-
`.content`, 30px wide.
18-
- [ ] CCB groups render in order: collapse-editor, sidebar-toggle, undo/redo,
19-
save, nav (search / back / forward), vertical filename label.
20-
- [ ] Sidebar's `data-minsize` is `30` (so drag can't auto-collapse below CCB).
21-
- [ ] Sidebar's resizer handle is shifted right of the CCB via CSS
22-
`transform: translateX(30px)` — clicking at `sidebar.right + 30` lands
23-
on the resizer element.
24-
- [ ] When the sidebar is hidden, the Resizer moves the handle to be a sibling
25-
of `#sidebar` inside `.main-view`; the same shift still applies.
26-
27-
## 2. CCB buttons
28-
29-
- [ ] Undo / Redo / Save buttons fire `Commands.EDIT_UNDO`, `EDIT_REDO`,
30-
`FILE_SAVE` respectively.
31-
- [ ] Search / Back / Forward / Show-in-Tree buttons still trigger the
32-
`NavigationProvider` behaviors after being moved out of `#mainNavBarRight`.
33-
- [ ] `#ccbSidebarToggleBtn` executes `VIEW_HIDE_SIDEBAR` and the icon flips
34-
`fa-angles-left``fa-angles-right` on panelCollapsed/panelExpanded.
35-
- [ ] The old `#sidebar-toggle-btn` in the menubar is NOT in the DOM.
36-
- [ ] `.ccb-group-nav` holds exactly `searchNav`, `navBackButton`,
37-
`navForwardButton` — no show-in-tree button lives in the CCB.
3814

3915
## 2a. #show-in-file-tree button (sidebar)
4016

test/spec/CentralControlBar-integ-test.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
define(function (require, exports, module) {
2424

2525
const SpecRunnerUtils = require("spec/SpecRunnerUtils"),
26+
DragTestUtils = require("spec/DragTestUtils"),
2627
Strings = require("strings");
2728

2829
const CCB_WIDTH = 30;
@@ -100,8 +101,31 @@ define(function (require, exports, module) {
100101
expect(Math.abs(ccbRect.right - contentLeft)).toBeLessThan(2);
101102
});
102103

103-
it("should set sidebar's data-minsize to 30 (so drag can't auto-collapse below CCB)", function () {
104-
expect(_$("#sidebar").attr("data-minsize")).toBe("30");
104+
it("should let the user fully collapse the sidebar by dragging its right edge all the way left", async function () {
105+
// Start from a comfortably wide sidebar so there's room to drag leftward past zero.
106+
SidebarView.resize(240);
107+
await awaitsFor(function () { return _$("#sidebar")[0].offsetWidth === 240; },
108+
"sidebar to settle at 240px", 2000);
109+
110+
const $resizer = _$("#sidebar > .horz-resizer");
111+
const sidebarLeft = _$("#sidebar")[0].getBoundingClientRect().left;
112+
const handleY = _$("#sidebar")[0].getBoundingClientRect().top + 100;
113+
114+
// Drag all the way left to the sidebar's own left edge — well past 0.
115+
// The sidebar is `collapsible`, so the Resizer should hide it entirely
116+
// and the CCB's sidebar-toggle remains as the way to bring it back.
117+
await DragTestUtils.dragFromElement($resizer[0], sidebarLeft - 50, handleY, testWindow);
118+
119+
expect(SidebarView.isVisible()).toBe(false);
120+
121+
// CCB stays put so the user can re-open the sidebar from the toggle.
122+
expect(_$("#ccbSidebarToggleBtn").is(":visible")).toBe(true);
123+
124+
// Restore for later tests.
125+
SidebarView.show();
126+
await awaitsFor(function () { return SidebarView.isVisible(); },
127+
"sidebar to come back for cleanup", 2000);
128+
SidebarView.resize(200);
105129
});
106130

107131
it("should shift the sidebar's resizer handle right by the CCB width via CSS", function () {

test/spec/DragTestUtils.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* GNU AGPL-3.0 License
3+
*
4+
* Copyright (c) 2021 - present core.ai . All rights reserved.
5+
*
6+
* This program is free software: you can redistribute it and/or modify it under
7+
* the terms of the GNU Affero General Public License as published by the
8+
* Free Software Foundation, either version 3 of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11+
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
16+
*
17+
*/
18+
19+
/**
20+
* Shared drag helpers for integ tests.
21+
*
22+
* The test harness loads the app inside a child window (testWindow). Production
23+
* code — most importantly `utils/Resizer` — attaches `mousemove` / `mouseup`
24+
* listeners to that window's document via jQuery, and commits its work in
25+
* `requestAnimationFrame` callbacks. To exercise those paths we must:
26+
*
27+
* - construct MouseEvents with `view: testWindow` so `event.view` is set and
28+
* jQuery's normalization picks up the correct document,
29+
* - dispatch `mousedown` against the drag handle itself (Resizer only arms
30+
* its pointer-tracking after seeing the mousedown),
31+
* - dispatch `mousemove` / `mouseup` against `testWindow.document`,
32+
* - yield for at least one `requestAnimationFrame` tick between moves so
33+
* `doRedraw` can run.
34+
*
35+
* Works under the harness's modern browser engines (the Chrome-based Tauri
36+
* shell, Firefox, Safari, and the standalone Electron test runner). The
37+
* MouseEvent constructor is the only spec-blessed path that sets coordinates
38+
* on bubbled events across those engines — `document.createEvent("MouseEvents")`
39+
* + `initMouseEvent` is deprecated and drops clientX/clientY in some Safari
40+
* versions, so we deliberately avoid it.
41+
*/
42+
define(function (require, exports, module) {
43+
44+
function _awaitFrames(win, n) {
45+
return new Promise(function (resolve) {
46+
let remaining = n;
47+
function tick() {
48+
remaining -= 1;
49+
if (remaining <= 0) {
50+
resolve();
51+
return;
52+
}
53+
win.requestAnimationFrame(tick);
54+
}
55+
win.requestAnimationFrame(tick);
56+
});
57+
}
58+
59+
function _fireMouse(target, type, x, y, win, buttons) {
60+
const ev = new win.MouseEvent(type, {
61+
bubbles: true,
62+
cancelable: true,
63+
view: win,
64+
clientX: x,
65+
clientY: y,
66+
button: 0,
67+
buttons: buttons
68+
});
69+
target.dispatchEvent(ev);
70+
}
71+
72+
/**
73+
* Simulate a user drag from the center of `startEl` to (endX, endY) inside
74+
* `testWindow`. Emits one mousedown on the handle, `steps` mousemoves along
75+
* the straight line to the destination, and a final mouseup, yielding to
76+
* rAF between moves so Resizer's doRedraw commits each increment.
77+
*
78+
* @param {DOMElement|jQuery} startEl The element the drag starts on
79+
* (typically the resizer handle).
80+
* @param {number} endX Final clientX (in testWindow viewport).
81+
* @param {number} endY Final clientY (in testWindow viewport).
82+
* @param {Window} testWindow The child window that owns the DOM.
83+
* @param {?number} steps Number of intermediate mousemoves
84+
* (default 8). More steps = smoother drag, more rAF waits.
85+
*/
86+
async function dragFromElement(startEl, endX, endY, testWindow, steps) {
87+
const el = startEl.jquery ? startEl[0] : startEl;
88+
const rect = el.getBoundingClientRect();
89+
const startX = rect.left + rect.width / 2;
90+
const startY = rect.top + rect.height / 2;
91+
const doc = testWindow.document;
92+
const moveSteps = (typeof steps === "number" && steps > 0) ? steps : 8;
93+
94+
_fireMouse(el, "mousedown", startX, startY, testWindow, 1);
95+
await _awaitFrames(testWindow, 1);
96+
97+
for (let i = 1; i <= moveSteps; i++) {
98+
const t = i / moveSteps;
99+
const x = startX + (endX - startX) * t;
100+
const y = startY + (endY - startY) * t;
101+
_fireMouse(doc, "mousemove", x, y, testWindow, 1);
102+
await _awaitFrames(testWindow, 1);
103+
}
104+
105+
_fireMouse(doc, "mouseup", endX, endY, testWindow, 0);
106+
await _awaitFrames(testWindow, 2);
107+
}
108+
109+
exports.dragFromElement = dragFromElement;
110+
});

0 commit comments

Comments
 (0)