mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
fix(terminal): address scrolling review and preserve upstream speed policies
Fix transport-timer regression coverage, zero-delta tile handling and capability documentation. Record isolated Codex 0.163.0 transcript and approval evidence; defer event/batch rate and coarse paging policy changes. Refs: https://github.com/johoja12/Codeman/issues/7
This commit is contained in:
1 parent
39d7c01b7b
commit
8cb3441dd9
11 files changed
+158
-139
No files matched your search
@@ -435,3 +435,12 @@ describe('capability shapes', () => {
|
||||
expect(paging.sort()).toEqual(['claude', 'codex', 'opencode']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('observed mouse-tracking wheel policy', () => {
|
||||
it('advertises and validates tracked Codex forwarding without a fictitious minimum version', () => {
|
||||
const codex = STOCK_CLIS.find((e) => e.id === 'codex')!;
|
||||
expect(codex.capabilities.wheelForward).toEqual({ mode: 'mouse-tracking' });
|
||||
expect(CliEntrySchema.safeParse(codex).success).toBe(true);
|
||||
expect(codex.capabilities.transcriptPageKeys).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -36,7 +36,18 @@ function loadTerminalUiHarness(
|
||||
// terminal-ui.js hangs CodemanTerminalInput off window; tests read it there.
|
||||
const windowRef: Record<string, any> = { ...windowGlobals };
|
||||
// Mutable so a test can move time (gesture gaps); defaults to a frozen clock.
|
||||
const clock = { now: () => 1_000 };
|
||||
const timers = new Map<number, { due: number; fn: () => void }>();
|
||||
let timerId = 0;
|
||||
const clock = {
|
||||
now: () => 1_000,
|
||||
flushTimers: () => {
|
||||
for (const [id, timer] of timers)
|
||||
if (timer.due <= clock.now()) {
|
||||
timers.delete(id);
|
||||
timer.fn();
|
||||
}
|
||||
},
|
||||
};
|
||||
const context = vm.createContext({
|
||||
window: windowRef,
|
||||
CodemanApp,
|
||||
@@ -44,8 +55,12 @@ function loadTerminalUiHarness(
|
||||
_crashDiag: { log: vi.fn() },
|
||||
performance: clock,
|
||||
requestAnimationFrame: (_fn: () => void) => 1,
|
||||
setTimeout: (_fn: () => void) => 1,
|
||||
clearTimeout: vi.fn(),
|
||||
setTimeout: (fn: () => void, ms = 0) => {
|
||||
const id = ++timerId;
|
||||
timers.set(id, { due: clock.now() + ms, fn });
|
||||
return id;
|
||||
},
|
||||
clearTimeout: (id: number) => timers.delete(id),
|
||||
Blob: function Blob() {},
|
||||
URL: { createObjectURL: () => 'blob:yield', revokeObjectURL: () => {} },
|
||||
Worker: function Worker(this: any) {
|
||||
@@ -206,24 +221,30 @@ describe('PageUp/PageDown fallback for a hollow local buffer (issue #205 round 2
|
||||
app._maybePageCliTranscript({ shiftKey: false }, -1); // first event: one PageUp
|
||||
app._maybePageCliTranscript({ shiftKey: false }, 1); // reversal inside the gap: accumulates, no page
|
||||
now += 200;
|
||||
clock.flushTimers();
|
||||
app._maybePageCliTranscript({ shiftKey: false }, -1); // a reversal after the gap: a PageUp at once
|
||||
now += 1_100;
|
||||
clock.flushTimers();
|
||||
app._maybePageCliTranscript({ shiftKey: false }, 1); // after a pause: a PageDown at once
|
||||
app._maybePageCliTranscript({ shiftKey: false }, 0.05); // sub-row jitter in the same gesture: nothing
|
||||
app._flushWheelSgrQueue();
|
||||
expect(sent).toEqual([{ id: 'sess-1', data: '\x1b[6~' }]);
|
||||
expect(sent).toEqual([
|
||||
{ id: 'sess-1', data: '\x1b[5~' },
|
||||
{ id: 'sess-1', data: '\x1b[6~' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('answers the first wheel notch and amortizes the page over subsequent notches', () => {
|
||||
// The first 100px notch responds immediately. Later notches repay the
|
||||
// pre-paid page, including when they arrive past the normal gesture gap.
|
||||
for (const gapMs of [250, 40]) {
|
||||
it('accumulates coarse wheel notches across pauses instead of prepaying each opening', () => {
|
||||
// Coarse events accumulate as on master, even across pauses; they do not
|
||||
// pre-pay a full page each time a new gesture starts.
|
||||
for (const gapMs of [250, 40, 1200]) {
|
||||
const { app, sent, clock } = hollowApp();
|
||||
let now = 1_000;
|
||||
clock.now = () => now;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
expect(app._maybePageCliTranscript({ shiftKey: false, deltaY: -100 }, -4)).toBe(true);
|
||||
now += gapMs;
|
||||
clock.flushTimers();
|
||||
}
|
||||
app._flushWheelSgrQueue();
|
||||
expect(sent).toEqual([{ id: 'sess-1', data: '\x1b[5~' }]);
|
||||
@@ -446,7 +467,14 @@ describe('page-key gestures across slow touch drags, slow wheel ticks and jitter
|
||||
h.app._flushWheelSgrQueue();
|
||||
return h.sent.map((s) => s.data).join('');
|
||||
};
|
||||
return { ...h, keys, advance: (ms: number) => (now += ms) };
|
||||
return {
|
||||
...h,
|
||||
keys,
|
||||
advance: (ms: number) => {
|
||||
now += ms;
|
||||
h.clock.flushTimers();
|
||||
},
|
||||
};
|
||||
}
|
||||
const count = (keys: string, key: string) => keys.split(key).length - 1;
|
||||
|
||||
@@ -533,7 +561,7 @@ describe('page-key gestures across slow touch drags, slow wheel ticks and jitter
|
||||
}
|
||||
// The opening event is a fresh gesture and pages; no reversal inside the
|
||||
// gap pre-pays, so the wobble sends nothing after it.
|
||||
expect(p.keys()).toBe(''); // obsolete queued direction cancelled before its flush
|
||||
expect(p.keys()).toBe(''); // the opening report was cancelled before its 40ms flush
|
||||
});
|
||||
|
||||
it('still pages a deliberate reversal at once after a pause', () => {
|
||||
@@ -541,7 +569,7 @@ describe('page-key gestures across slow touch drags, slow wheel ticks and jitter
|
||||
p.app._maybePageCliTranscript({ shiftKey: false, deltaY: -25 }, -1);
|
||||
p.advance(200);
|
||||
p.app._maybePageCliTranscript({ shiftKey: false, deltaY: 25 }, 1);
|
||||
expect(p.keys()).toBe(DOWN);
|
||||
expect(p.keys()).toBe(UP + DOWN);
|
||||
});
|
||||
|
||||
it('does not let a reversal inherit the other direction’s debt', () => {
|
||||
@@ -863,28 +891,43 @@ describe('consistent scroll gesture adapters', () => {
|
||||
expect(app._wheelSgrQueue || '').toBe('');
|
||||
});
|
||||
|
||||
it('answers the first ordinary Codex wheel event, without paging every event', () => {
|
||||
it('preserves accumulated coarse Codex wheel fallback travel', () => {
|
||||
const { app, sent } = hollowApp({ mode: 'codex', rows: 43 });
|
||||
app._maybePageCliTranscript({ deltaY: -100 }, -4);
|
||||
app._flushWheelSgrQueue();
|
||||
expect(sent).toEqual([{ id: 'sess-1', data: '\x1b[5~' }]);
|
||||
expect(sent).toEqual([]);
|
||||
for (let i = 0; i < 5; i++) app._maybePageCliTranscript({ deltaY: -100 }, -4);
|
||||
app._flushWheelSgrQueue();
|
||||
expect(sent).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('bounds a sustained burst and cancels obsolete direction before transport', () => {
|
||||
it('preserves sustained report density and cancels obsolete direction before transport', () => {
|
||||
const { coalesceScrollBytes } = loadTerminalUiHarness().windowRef.CodemanTerminalInput;
|
||||
const up = '\x1b[<64;1;1M';
|
||||
const down = '\x1b[<65;1;1M';
|
||||
let queued = '';
|
||||
for (let i = 0; i < 100; i++) queued = coalesceScrollBytes(queued, up);
|
||||
expect(queued).toBe(up.repeat(5));
|
||||
expect(queued).toBe(up.repeat(100));
|
||||
expect(coalesceScrollBytes(queued, down)).toBe(down);
|
||||
expect(coalesceScrollBytes('\x1b[5~'.repeat(3), '\x1b[5~')).toBe('\x1b[5~'.repeat(3));
|
||||
expect(coalesceScrollBytes('\x1b[5~'.repeat(3), '\x1b[5~')).toBe('\x1b[5~'.repeat(4));
|
||||
expect(coalesceScrollBytes('\x1b[5~', '\x1b[6~')).toBe('\x1b[6~');
|
||||
});
|
||||
|
||||
it('preserves all 120 reports of a 30-frame touch fling with real batch deadlines', () => {
|
||||
const { app, sent, clock } = hollowApp();
|
||||
let now = 1000;
|
||||
clock.now = () => now;
|
||||
app._clientPointToCell = () => ({ col: 1, row: 1 });
|
||||
for (let frame = 0; frame < 30; frame++) {
|
||||
app._sendSyntheticSgrWheel(0, 0, -4);
|
||||
now += 16;
|
||||
clock.flushTimers();
|
||||
}
|
||||
now += 40;
|
||||
clock.flushTimers();
|
||||
expect(sent.map((s) => s.data).join('')).toBe('\x1b[<64;1;1M'.repeat(120));
|
||||
});
|
||||
|
||||
it('does not deliver a queued wheel batch to a newly selected session', () => {
|
||||
const { app, sent } = hollowApp();
|
||||
app._queueScrollBytes('\x1b[5~');
|
||||
@@ -939,16 +982,6 @@ describe('native Codex scrolling instead of an initial page jump', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('preserves the Codex dialog guard for primary and tiled routing', () => {
|
||||
const { app } = hollowApp({ mode: 'codex', cliMouseTracking: true });
|
||||
app.sessions.set('tile', { mode: 'codex', cliMouseTracking: true });
|
||||
app.tabAlerts = new Map([['sess-1', 'action']]);
|
||||
expect(app._shouldForwardWheelToApp({ shiftKey: false })).toBe(false);
|
||||
expect(app._shouldForwardWheelToApp({ shiftKey: false }, { sessionId: 'tile' })).toBe(true);
|
||||
app.tabAlerts.set('tile', 'action');
|
||||
expect(app._shouldForwardWheelToApp({ shiftKey: false }, { sessionId: 'tile' })).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps local-history opt-out and native xterm mouse routing', () => {
|
||||
const { app } = hollowApp({ mode: 'codex', cliMouseTracking: true });
|
||||
app.loadAppSettingsFromStorage = () => ({ terminalWheelLocalScrollback: true });
|
||||
@@ -959,9 +992,9 @@ describe('native Codex scrolling instead of an initial page jump', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('one native tick per wheel event', () => {
|
||||
describe('native wheel travel preserves upstream units', () => {
|
||||
it('normalizes coarse Chrome, Firefox and page-unit wheel events without guessing the device', () => {
|
||||
const { wheelDeltaNativeLines } = loadTerminalUiHarness().windowRef.CodemanTerminalInput;
|
||||
const { wheelDeltaLines } = loadTerminalUiHarness().windowRef.CodemanTerminalInput;
|
||||
for (const [deltaY, deltaMode] of [
|
||||
[100, 0],
|
||||
[120, 0],
|
||||
@@ -969,21 +1002,25 @@ describe('one native tick per wheel event', () => {
|
||||
[3, 1],
|
||||
[1, 2],
|
||||
]) {
|
||||
expect(wheelDeltaNativeLines({ deltaY, deltaMode }, 43)).toBe(1);
|
||||
expect(wheelDeltaNativeLines({ deltaY: -deltaY, deltaMode }, 43)).toBe(-1);
|
||||
expect(wheelDeltaLines({ deltaY, deltaMode }, 43)).toBe(
|
||||
deltaMode === 0 ? deltaY / 25 : deltaMode === 1 ? deltaY : deltaY * 43
|
||||
);
|
||||
expect(wheelDeltaLines({ deltaY: -deltaY, deltaMode }, 43)).toBe(
|
||||
-(deltaMode === 0 ? deltaY / 25 : deltaMode === 1 ? deltaY : deltaY * 43)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps fractional touchpad distance and primary/tile normalization identical', () => {
|
||||
const { app, windowRef } = hollowApp({ mode: 'codex', cliMouseTracking: true });
|
||||
const { wheelDeltaNativeLines, wheelTicksForGesture } = windowRef.CodemanTerminalInput;
|
||||
const { wheelDeltaLines, wheelTicksForGesture } = windowRef.CodemanTerminalInput;
|
||||
let state;
|
||||
let ticks = 0;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const ev = { deltaY: -3, deltaMode: 0 };
|
||||
const lines = wheelDeltaNativeLines(ev, 43);
|
||||
const lines = wheelDeltaLines(ev, 43);
|
||||
expect(lines).toBe(-0.12);
|
||||
expect(app._wheelScrollNativeLines(ev)).toBe(lines);
|
||||
expect(app._wheelScrollLinesFloat(ev)).toBe(lines);
|
||||
const step = wheelTicksForGesture(state, lines, i * 10);
|
||||
state = step.state;
|
||||
ticks += step.ticks;
|
||||
|
||||
@@ -319,11 +319,11 @@ describe('a fullscreen Claude tile forwards the wheel to the CLI as SGR reports'
|
||||
|
||||
expect(up.preventDefault).toHaveBeenCalled();
|
||||
expect(up.stopPropagation).toHaveBeenCalled();
|
||||
expect(ws.inputFrames()).toEqual([{ t: 'i', d: UP }]); // first report never waits for a timer
|
||||
expect(flushed(ws)).toEqual([{ t: 'i', d: UP }]); // no seq: never persisted
|
||||
expect(ws.inputFrames()).toEqual([{ t: 'i', d: UP.repeat(3) }]); // first report never waits for a timer
|
||||
expect(flushed(ws)).toEqual([{ t: 'i', d: UP.repeat(3) }]); // no seq: never persisted
|
||||
|
||||
mount.fire('wheel', wheelLines(2, AT));
|
||||
expect(flushed(ws).at(-1)).toEqual({ t: 'i', d: DOWN });
|
||||
expect(flushed(ws).at(-1)).toEqual({ t: 'i', d: DOWN.repeat(2) });
|
||||
});
|
||||
|
||||
it('uses fractional native scrolling for a tracked Codex tile, never PageUp', async () => {
|
||||
@@ -334,14 +334,14 @@ describe('a fullscreen Claude tile forwards the wheel to the CLI as SGR reports'
|
||||
expect(flushed(ws)).toEqual([{ t: 'i', d: UP }]);
|
||||
});
|
||||
|
||||
it('normalizes a pixel wheel event to one native tick, including accelerated deltas', async () => {
|
||||
it('preserves coarse pixel wheel travel and caps individual accelerated events', async () => {
|
||||
const { ws, mount } = await connectTile(makeApp({ 's-tile': FULLSCREEN }), { mode: 'claude' });
|
||||
|
||||
mount.fire('wheel', wheel(-100, AT));
|
||||
expect(flushed(ws)).toEqual([{ t: 'i', d: UP }]);
|
||||
expect(flushed(ws)).toEqual([{ t: 'i', d: UP.repeat(4) }]);
|
||||
|
||||
mount.fire('wheel', wheelLines(-1000, AT));
|
||||
expect(flushed(ws).at(-1)).toEqual({ t: 'i', d: UP });
|
||||
expect(flushed(ws).at(-1)).toEqual({ t: 'i', d: UP.repeat(5) });
|
||||
});
|
||||
|
||||
it('accumulates precision touchpad deltas and consumes a pure horizontal swipe without sending', async () => {
|
||||
@@ -366,7 +366,18 @@ describe('a fullscreen Claude tile forwards the wheel to the CLI as SGR reports'
|
||||
mount.fire('wheel', wheelLines(-2, AT));
|
||||
|
||||
mount.fire('wheel', wheelLines(-2, AT));
|
||||
expect(ws.inputFrames()).toEqual([{ t: 'i', d: UP }]);
|
||||
expect(ws.inputFrames()).toEqual([{ t: 'i', d: UP.repeat(2) }]);
|
||||
expect(flushed(ws)).toEqual([
|
||||
{ t: 'i', d: UP.repeat(2) },
|
||||
{ t: 'i', d: UP.repeat(4) },
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not discard queued reports when a zero-delta wheel arrives', async () => {
|
||||
const { ws, mount } = await connectTile(makeApp({ 's-tile': FULLSCREEN }), { mode: 'claude' });
|
||||
mount.fire('wheel', wheelLines(-1, AT));
|
||||
mount.fire('wheel', wheelLines(-2, AT));
|
||||
mount.fire('wheel', wheel(0, AT));
|
||||
expect(flushed(ws)).toEqual([
|
||||
{ t: 'i', d: UP },
|
||||
{ t: 'i', d: UP.repeat(2) },
|
||||
@@ -451,14 +462,14 @@ describe('a fullscreen Claude tile forwards the wheel to the CLI as SGR reports'
|
||||
const primary = makeApp({ other: FULLSCREEN }, 'other') as App & {
|
||||
terminal: unknown;
|
||||
_sendSyntheticSgrWheel(x: number, y: number, lines: number): void;
|
||||
_wheelScrollNativeLines(ev: unknown): number;
|
||||
_wheelScrollLinesFloat(ev: unknown): number;
|
||||
_flushWheelSgrQueue(): void;
|
||||
};
|
||||
const sent: string[] = [];
|
||||
primary._sendInputEphemeral = (_id: string, data: string) => sent.push(data);
|
||||
primary.terminal = new FakeTerminal({});
|
||||
for (const ev of sequence) {
|
||||
primary._sendSyntheticSgrWheel(ev.clientX, ev.clientY, primary._wheelScrollNativeLines(ev));
|
||||
primary._sendSyntheticSgrWheel(ev.clientX, ev.clientY, primary._wheelScrollLinesFloat(ev));
|
||||
primary._flushWheelSgrQueue();
|
||||
}
|
||||
|
||||
@@ -546,11 +557,11 @@ describe('page-key travel, cap and coalescing', () => {
|
||||
FakeFit.proposed = { cols: 80, rows: 36 };
|
||||
});
|
||||
|
||||
it('answers sub-page travel immediately and repays it on the next event', async () => {
|
||||
it('accumulates coarse sub-page travel before sending one page', async () => {
|
||||
const { ws, mount } = await connectTile(makeApp({ 's-tile': { mode: 'opencode' } }));
|
||||
|
||||
mount.fire('wheel', wheelLines(-10));
|
||||
expect(flushed(ws)).toEqual([{ t: 'i', d: PAGE_UP }]);
|
||||
expect(flushed(ws)).toEqual([]);
|
||||
mount.fire('wheel', wheelLines(-10));
|
||||
|
||||
expect(flushed(ws)).toEqual([{ t: 'i', d: PAGE_UP }]);
|
||||
@@ -614,6 +625,7 @@ describe("gesture handling is the primary pane's, through the shared pure helper
|
||||
clock.now = () => now;
|
||||
return (ms: number) => {
|
||||
now += ms;
|
||||
vi.advanceTimersByTime(ms);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -750,7 +762,7 @@ describe("gesture handling is the primary pane's, through the shared pure helper
|
||||
.map((f) => f.d)
|
||||
.join('');
|
||||
// One page for the slow ticks, one for the wobble's opening event, nothing more.
|
||||
expect(tileBytes).toBe(''); // rapid reversals cancel unsent movement
|
||||
expect(tileBytes).toBe(PAGE_UP); // the first slow tick flushed before the later wobble
|
||||
|
||||
const primary = makeApp({ other: { mode: 'codex' } }, 'other') as App & {
|
||||
terminal: unknown;
|
||||
@@ -909,7 +921,7 @@ describe('rows the tile pushed above the screen itself are not history', () => {
|
||||
const first = wheelLines(-6);
|
||||
mount.fire('wheel', first);
|
||||
expect(first.preventDefault).toHaveBeenCalled();
|
||||
expect(flushed(ws)).toEqual([{ t: 'i', d: PAGE_UP }]);
|
||||
expect(flushed(ws)).toEqual([]);
|
||||
mount.fire('wheel', wheelLines(-6));
|
||||
expect(flushed(ws)).toEqual([{ t: 'i', d: PAGE_UP }]);
|
||||
});
|
||||
|
||||
@@ -738,17 +738,17 @@ describe('terminal touch tap mouse guard', () => {
|
||||
app.terminal = { rows: 40 };
|
||||
|
||||
// DOM_DELTA_PIXEL (Chrome/WebKit, and every trackpad): ~110px per notch.
|
||||
expect(app._wheelScrollLines({ deltaY: 110, deltaX: 0, deltaMode: 0, shiftKey: false })).toBe(4);
|
||||
expect(app._wheelScrollLinesFloat({ deltaY: 110, deltaX: 0, deltaMode: 0, shiftKey: false })).toBe(4.4);
|
||||
// DOM_DELTA_LINE (Firefox mouse wheel): deltaY is already lines. Read as
|
||||
// pixels this rounded to 0 and fell through to the ±1 fallback.
|
||||
expect(app._wheelScrollLines({ deltaY: 3, deltaX: 0, deltaMode: 1, shiftKey: false })).toBe(3);
|
||||
expect(app._wheelScrollLines({ deltaY: -3, deltaX: 0, deltaMode: 1, shiftKey: false })).toBe(-3);
|
||||
expect(app._wheelScrollLinesFloat({ deltaY: 3, deltaX: 0, deltaMode: 1, shiftKey: false })).toBe(3);
|
||||
expect(app._wheelScrollLinesFloat({ deltaY: -3, deltaX: 0, deltaMode: 1, shiftKey: false })).toBe(-3);
|
||||
// DOM_DELTA_PAGE: one page is one screenful.
|
||||
expect(app._wheelScrollLines({ deltaY: 1, deltaX: 0, deltaMode: 2, shiftKey: false })).toBe(40);
|
||||
expect(app._wheelScrollLinesFloat({ deltaY: 1, deltaX: 0, deltaMode: 2, shiftKey: false })).toBe(40);
|
||||
// A pure horizontal swipe must not fall through to a phantom -1.
|
||||
expect(app._wheelScrollLines({ deltaY: 0, deltaX: 90, deltaMode: 0, shiftKey: false })).toBe(0);
|
||||
expect(app._wheelScrollLinesFloat({ deltaY: 0, deltaX: 90, deltaMode: 0, shiftKey: false })).toBe(0);
|
||||
// Shift + macOS trackpad reports the magnitude on deltaX (issue #154).
|
||||
expect(app._wheelScrollLines({ deltaY: 0, deltaX: -100, deltaMode: 0, shiftKey: true })).toBe(-4);
|
||||
expect(app._wheelScrollLinesFloat({ deltaY: 0, deltaX: -100, deltaMode: 0, shiftKey: true })).toBe(-4);
|
||||
});
|
||||
|
||||
it('wheel: gates claude forwarding on CLI version 2.1.187+ (unknown or older stays local)', () => {
|
||||
@@ -838,19 +838,19 @@ describe('terminal touch tap mouse guard', () => {
|
||||
const { app } = loadTerminalUiHarness();
|
||||
|
||||
// Plain vertical wheel: unchanged, driven by deltaY.
|
||||
expect(app._wheelScrollLines({ shiftKey: false, deltaX: 0, deltaY: 100 })).toBe(4);
|
||||
expect(app._wheelScrollLines({ shiftKey: false, deltaX: 0, deltaY: -50 })).toBe(-2);
|
||||
expect(app._wheelScrollLinesFloat({ shiftKey: false, deltaX: 0, deltaY: 100 })).toBe(4);
|
||||
expect(app._wheelScrollLinesFloat({ shiftKey: false, deltaX: 0, deltaY: -50 })).toBe(-2);
|
||||
|
||||
// Shift on a macOS trackpad: deltaY≈0, deltaX carries direction+magnitude.
|
||||
// Old code collapsed this to a fixed -1; now it tracks the horizontal delta.
|
||||
expect(app._wheelScrollLines({ shiftKey: true, deltaX: -100, deltaY: 0 })).toBe(-4); // scroll up
|
||||
expect(app._wheelScrollLines({ shiftKey: true, deltaX: 75, deltaY: 0 })).toBe(3); // scroll down
|
||||
expect(app._wheelScrollLinesFloat({ shiftKey: true, deltaX: -100, deltaY: 0 })).toBe(-4); // scroll up
|
||||
expect(app._wheelScrollLinesFloat({ shiftKey: true, deltaX: 75, deltaY: 0 })).toBe(3); // scroll down
|
||||
|
||||
// Shift with a real vertical wheel (mouse): deltaY dominates, deltaX ignored.
|
||||
expect(app._wheelScrollLines({ shiftKey: true, deltaX: 2, deltaY: 100 })).toBe(4);
|
||||
expect(app._wheelScrollLinesFloat({ shiftKey: true, deltaX: 2, deltaY: 100 })).toBe(4);
|
||||
|
||||
// Sub-25px delta still nudges one line in the gesture's direction.
|
||||
expect(app._wheelScrollLines({ shiftKey: true, deltaX: -5, deltaY: 0 })).toBe(-1);
|
||||
// Sub-25px deltas retain their fractional travel.
|
||||
expect(app._wheelScrollLinesFloat({ shiftKey: true, deltaX: -5, deltaY: 0 })).toBe(-0.2);
|
||||
});
|
||||
|
||||
it('wheel: encodes SGR 64/65 ticks, caps per event, and coalesces into one flush', () => {
|
||||
|
||||
Reference in new issue
Block a user