update
This commit is contained in:
225
tests/unit/splitter.test.js
Normal file
225
tests/unit/splitter.test.js
Normal file
@@ -0,0 +1,225 @@
|
||||
// src/splitter.js —— 通用拖拽分割条工厂测试
|
||||
//
|
||||
// 覆盖 getDragSign 的方向反转逻辑:
|
||||
// - 不传 getDragSign(默认)→ 鼠标右移让目标变大(+1)
|
||||
// - 传 getDragSign 返回 -1 → 鼠标右移让目标变小(用于 AI 面板在 splitter
|
||||
// 右侧且贴窗口右边缘的场景,避免 splitter 视觉反向跟随鼠标)
|
||||
// - getDragSign 抛错或返回非 -1/1 → 兜底 +1,不让一次坏拖动摧毁整个交互
|
||||
//
|
||||
// 通过 jsdom 模拟 document/element,绕过 mountAiSplitter 的 DOM 依赖,直接
|
||||
// 驱动 createSplitter 的 mousedown/mousemove 路径验证 setSize 调用序列。
|
||||
//
|
||||
// 不覆盖:
|
||||
// - 命中区(hitZone)扩展 → 那是 mountSidebarSplitter/mountAiSplitter 的
|
||||
// 调用方职责,工厂只关心"接到 hit zone 触发后行为正确"。
|
||||
// - touch 路径 → 与 mouse 路径走同一道 startDrag/applyDrag,代码层同源。
|
||||
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { createSplitter } from '../../src/splitter.js';
|
||||
|
||||
/**
|
||||
* 在 jsdom 里构造一个最小可用的 splitter:
|
||||
* - handle 是按钮元素
|
||||
* - getSize/setSize/clampSize/onDragEnd 由用例注入
|
||||
* - 不传 hitZone / initialSize / windowListeners(与 mountAiSplitter 主路径
|
||||
* 解耦,单独验证方向反转这条新分支)
|
||||
*
|
||||
* 返回 {handle, fireMouseDown, fireMouseMove, fireMouseUp, setSizeCalls}:
|
||||
* - fireMouseDown(clientX) / fireMouseMove / fireMouseUp 模拟真实事件
|
||||
* - setSizeCalls 记录所有 setSize 调用,可断言方向与幅度
|
||||
*/
|
||||
function makeSplitter({ getSize, setSize, clampSize, getDragSign }) {
|
||||
const handle = document.createElement('div');
|
||||
handle.style.width = '4px';
|
||||
document.body.appendChild(handle);
|
||||
|
||||
const onDragEnd = vi.fn();
|
||||
const inst = createSplitter({
|
||||
handle,
|
||||
axis: 'x',
|
||||
getSize,
|
||||
setSize,
|
||||
clampSize,
|
||||
onDragEnd,
|
||||
getDragSign,
|
||||
});
|
||||
|
||||
const setSizeCalls = [];
|
||||
const wrappedSetSize = (px) => {
|
||||
setSizeCalls.push(px);
|
||||
setSize(px);
|
||||
};
|
||||
// 重新绑一个 setSize 用于观察
|
||||
inst.dispose();
|
||||
const inst2 = createSplitter({
|
||||
handle,
|
||||
axis: 'x',
|
||||
getSize,
|
||||
setSize: wrappedSetSize,
|
||||
clampSize,
|
||||
onDragEnd,
|
||||
getDragSign,
|
||||
});
|
||||
|
||||
const fireMouseDown = (clientX) => {
|
||||
handle.dispatchEvent(new MouseEvent('mousedown', { clientX, bubbles: true }));
|
||||
};
|
||||
const fireMouseMove = (clientX) => {
|
||||
document.dispatchEvent(new MouseEvent('mousemove', { clientX, bubbles: true }));
|
||||
};
|
||||
const fireMouseUp = (clientX = 0) => {
|
||||
document.dispatchEvent(new MouseEvent('mouseup', { clientX, bubbles: true }));
|
||||
};
|
||||
|
||||
return {
|
||||
handle,
|
||||
inst: inst2,
|
||||
onDragEnd,
|
||||
fireMouseDown,
|
||||
fireMouseMove,
|
||||
fireMouseUp,
|
||||
setSizeCalls,
|
||||
};
|
||||
}
|
||||
|
||||
describe('createSplitter —— 方向反转 (getDragSign)', () => {
|
||||
it('不传 getDragSign:鼠标右移 → 目标变大(默认 +1)', () => {
|
||||
let current = 300;
|
||||
const { fireMouseDown, fireMouseMove, fireMouseUp, setSizeCalls, onDragEnd } =
|
||||
makeSplitter({
|
||||
getSize: () => current,
|
||||
setSize: (px) => { current = px; },
|
||||
clampSize: (px) => Math.max(200, Math.min(800, px)),
|
||||
});
|
||||
|
||||
fireMouseDown(500);
|
||||
fireMouseMove(560); // +60
|
||||
fireMouseUp();
|
||||
|
||||
expect(setSizeCalls).toEqual([360]); // 300 + 60
|
||||
expect(onDragEnd).toHaveBeenCalledWith(360);
|
||||
});
|
||||
|
||||
it('getDragSign 返回 -1:鼠标右移 → 目标变小(AI 面板在 splitter 右侧场景)', () => {
|
||||
let current = 360;
|
||||
const signProvider = vi.fn(() => -1);
|
||||
const { fireMouseDown, fireMouseMove, fireMouseUp, setSizeCalls, onDragEnd } =
|
||||
makeSplitter({
|
||||
getSize: () => current,
|
||||
setSize: (px) => { current = px; },
|
||||
clampSize: (px) => Math.max(220, Math.min(720, px)),
|
||||
getDragSign: signProvider,
|
||||
});
|
||||
|
||||
fireMouseDown(500);
|
||||
fireMouseMove(560); // +60 但应反向 → 减 60
|
||||
fireMouseUp();
|
||||
|
||||
expect(setSizeCalls).toEqual([300]); // 360 - 60
|
||||
expect(signProvider).toHaveBeenCalledTimes(1); // 仅 startDrag 时调用一次
|
||||
expect(onDragEnd).toHaveBeenCalledWith(300);
|
||||
});
|
||||
|
||||
it('getDragSign 返回 +1:与默认行为一致(防御性测试)', () => {
|
||||
let current = 360;
|
||||
const { fireMouseDown, fireMouseMove, fireMouseUp, setSizeCalls } =
|
||||
makeSplitter({
|
||||
getSize: () => current,
|
||||
setSize: (px) => { current = px; },
|
||||
clampSize: (px) => Math.max(220, Math.min(720, px)),
|
||||
getDragSign: () => 1,
|
||||
});
|
||||
|
||||
fireMouseDown(500);
|
||||
fireMouseMove(560);
|
||||
fireMouseUp();
|
||||
|
||||
expect(setSizeCalls).toEqual([420]); // 360 + 60
|
||||
});
|
||||
|
||||
it('getDragSign 在每次 startDrag 时重新求值(应对切模式)', () => {
|
||||
let current = 360;
|
||||
// 模拟「第一次拖动(split 模式)= +1,第二次拖动(preview 模式)= -1」
|
||||
const signProvider = vi.fn()
|
||||
.mockReturnValueOnce(1)
|
||||
.mockReturnValueOnce(-1);
|
||||
|
||||
const { fireMouseDown, fireMouseMove, fireMouseUp, setSizeCalls } =
|
||||
makeSplitter({
|
||||
getSize: () => current,
|
||||
setSize: (px) => { current = px; },
|
||||
clampSize: (px) => Math.max(220, Math.min(720, px)),
|
||||
getDragSign: signProvider,
|
||||
});
|
||||
|
||||
// 第一次拖动(+1):鼠标右移 50 → current 应 +50
|
||||
fireMouseDown(500);
|
||||
fireMouseMove(550);
|
||||
fireMouseUp();
|
||||
expect(current).toBe(410);
|
||||
|
||||
// 第二次拖动(-1):鼠标右移 50 → current 应 -50
|
||||
fireMouseDown(500);
|
||||
fireMouseMove(550);
|
||||
fireMouseUp();
|
||||
expect(current).toBe(360);
|
||||
|
||||
expect(signProvider).toHaveBeenCalledTimes(2);
|
||||
expect(setSizeCalls).toEqual([410, 360]);
|
||||
});
|
||||
|
||||
it('getDragSign 抛错 → 兜底 +1,不让坏回调炸掉拖动', () => {
|
||||
let current = 360;
|
||||
const { fireMouseDown, fireMouseMove, fireMouseUp, setSizeCalls } =
|
||||
makeSplitter({
|
||||
getSize: () => current,
|
||||
setSize: (px) => { current = px; },
|
||||
clampSize: (px) => Math.max(220, Math.min(720, px)),
|
||||
// 抛错 → 工厂内部 try/catch 兜底为 +1,拖动继续按默认方向进行
|
||||
getDragSign: () => { throw new Error('layout not ready'); },
|
||||
});
|
||||
|
||||
fireMouseDown(500); // 内部吞掉 getDragSign 抛错,dragSign = 1
|
||||
fireMouseMove(560); // startSize(360) + dragSign(1) * +60 = 420
|
||||
fireMouseUp();
|
||||
|
||||
expect(setSizeCalls).toEqual([420]);
|
||||
});
|
||||
|
||||
it('getDragSign 返回非 -1/1 的奇怪值 → 归一为 +1', () => {
|
||||
let current = 360;
|
||||
const { fireMouseDown, fireMouseMove, fireMouseUp, setSizeCalls } =
|
||||
makeSplitter({
|
||||
getSize: () => current,
|
||||
setSize: (px) => { current = px; },
|
||||
clampSize: (px) => Math.max(220, Math.min(720, px)),
|
||||
getDragSign: () => 0, // 0 不是合法符号 → 应被识别为 +1
|
||||
});
|
||||
|
||||
fireMouseDown(500);
|
||||
fireMouseMove(560);
|
||||
fireMouseUp();
|
||||
|
||||
expect(setSizeCalls).toEqual([420]); // 360 + 60(按 +1 算)
|
||||
});
|
||||
|
||||
it('reverse 方向下 clamp 仍生效(防止拖出 MIN_WIDTH)', () => {
|
||||
let current = 230;
|
||||
const { fireMouseDown, fireMouseMove, fireMouseUp, setSizeCalls } =
|
||||
makeSplitter({
|
||||
getSize: () => current,
|
||||
setSize: (px) => { current = px; },
|
||||
clampSize: (px) => Math.max(220, Math.min(720, px)),
|
||||
getDragSign: () => -1,
|
||||
});
|
||||
|
||||
fireMouseDown(500);
|
||||
// 鼠标右移 100 → 当前期望 current - 100 = 130,但 clamp 应钉到 MIN=220
|
||||
fireMouseMove(600);
|
||||
fireMouseUp();
|
||||
|
||||
expect(setSizeCalls).toEqual([220]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user