Files
bruno/packages/bruno-app/src/hooks/useResizableColumns/index.spec.js
sachin-thakur-bruno 7af6150f42 fix(request-panel)/fixed overflow size column issue on first load (#8525)
* fix(request-panel)/fixed overflow size column issue on first load

* added tests

* refactor the total width calculation based on available space
2026-07-08 12:43:53 +05:30

290 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const { describe, it, expect, jest, beforeEach } = require('@jest/globals');
import { render, act } from '@testing-library/react';
import React from 'react';
import { useResizableColumns } from './index';
const CONTAINER_WIDTH = 1000;
const DEFAULT_WIDTHS = [100, 200, 400, 200, 100]; // sums to CONTAINER_WIDTH
const MIN_COL_WIDTH = 60;
// Captures the latest hook return value on each render
let hookValue;
function Fixture({ defaultWidths = DEFAULT_WIDTHS, minColWidth = MIN_COL_WIDTH }) {
const hook = useResizableColumns({ defaultWidths, minColWidth });
hookValue = hook;
return <div ref={hook.containerRef} />;
}
const setup = (props = {}) => render(<Fixture {...props} />);
const fireMouse = (type, clientX) => {
act(() => {
document.dispatchEvent(new MouseEvent(type, { clientX, bubbles: true }));
});
};
const startDrag = (separatorIdx, startX) => {
act(() => {
hookValue.handleResizeStart(
{ clientX: startX, preventDefault: jest.fn(), stopPropagation: jest.fn() },
separatorIdx
);
});
};
describe('useResizableColumns', () => {
let triggerResize;
beforeEach(() => {
hookValue = null;
global.ResizeObserver = class {
constructor(cb) {
triggerResize = (w) => cb([{ contentRect: { width: w } }]);
}
observe() {
triggerResize(CONTAINER_WIDTH);
}
disconnect() {}
};
});
describe('initial state (before measurement)', () => {
beforeEach(() => {
global.ResizeObserver = class {
constructor() {}
observe() {}
disconnect() {}
};
setup();
});
it('returns fallback state before measurement', () => {
expect(hookValue.colWidths).toBeNull();
expect(hookValue.gridTemplateColumns)
.toBe(`repeat(${DEFAULT_WIDTHS.length}, 1fr)`);
expect(hookValue.separatorPositions).toEqual([]);
});
});
describe('container measurement', () => {
it('columns sum to container width after first measurement', () => {
setup();
const total = hookValue.colWidths.reduce((s, w) => s + w, 0);
expect(total).toBe(CONTAINER_WIDTH);
});
it('falls back to equal distribution when container is narrower than minColWidth * columns', () => {
// 5 columns × 60px min = 300px minimum; container at 200px is unsatisfiable
setup();
act(() => { triggerResize(200); });
const total = hookValue.colWidths.reduce((s, w) => s + w, 0);
expect(total).toBe(200);
});
it('ignores a zero-width measurement', () => {
global.ResizeObserver = class {
constructor(cb) {
triggerResize = (w) => cb([{ contentRect: { width: w } }]);
}
observe() {
triggerResize(0);
}
disconnect() {}
};
setup();
expect(hookValue.colWidths).toBeNull();
});
it('scales existing widths proportionally on container resize', () => {
setup();
act(() => {
triggerResize(800);
});
const total = hookValue.colWidths.reduce((s, w) => s + w, 0);
expect(total).toBe(800);
});
it('preserves manually-resized proportions when container resizes again', () => {
setup();
startDrag(1, 500);
fireMouse('mousemove', 600);
fireMouse('mouseup', 600);
const userWidths = [...hookValue.colWidths];
act(() => {
triggerResize(1500);
});
expect(
hookValue.colWidths.reduce((s, w) => s + w, 0)
).toBe(1500);
expect(hookValue.colWidths[1]).toBeCloseTo(
userWidths[1] * 1.5,
0
);
});
});
describe('scaling invariants (regression: Devtools network tab "Size" column overflow)', () => {
const NETWORK_TAB_WIDTHS = [80, 70, 180, 300, 110, 100, 80];
const assertNoOverflowAndRespectsMin = (widths, minColWidth) => {
setup({ defaultWidths: widths, minColWidth });
const from = minColWidth * widths.length;
const to = Math.ceil(widths.reduce((s, w) => s + w, 0) * 1.5);
// Step by at least 2px: the hook itself treats a <=1px delta as noise
// and skips recomputing (see the ResizeObserver callback in index.js),
// which is unrelated to the scaling algorithm under test here.
const step = Math.max(2, Math.floor((to - from) / 200));
for (let target = from; target <= to; target += step) {
act(() => { triggerResize(target); });
const total = hookValue.colWidths.reduce((s, w) => s + w, 0);
const minVal = Math.min(...hookValue.colWidths);
expect(total).toBe(target);
expect(minVal).toBeGreaterThanOrEqual(minColWidth);
}
};
it('never overflows or dips under minColWidth across a sweep of container widths (real network-tab columns)', () => {
assertNoOverflowAndRespectsMin(NETWORK_TAB_WIDTHS, 60);
});
it('never overflows or dips under minColWidth across a sweep of container widths (generic columns)', () => {
assertNoOverflowAndRespectsMin(DEFAULT_WIDTHS, MIN_COL_WIDTH);
});
it('exact reproduction: shrinking to 380px (below minColWidth*7=420) still clips instead of overflowing', () => {
setup({ defaultWidths: NETWORK_TAB_WIDTHS, minColWidth: 60 });
act(() => { triggerResize(380); });
const total = hookValue.colWidths.reduce((s, w) => s + w, 0);
expect(total).toBe(380);
});
it('handles evenly-weighted columns without rounding pile-up pushing a column under minColWidth', () => {
// With naive per-column Math.round(), 4 equal-weight columns at this
// exact target used to round 3 columns up to minColWidth and leave the
// 4th short of it.
assertNoOverflowAndRespectsMin([20, 20, 20, 20], 10);
});
});
describe('drag resize', () => {
beforeEach(() => {
setup();
});
it('dragging right grows left column and shrinks right column equally', () => {
const before = [...hookValue.colWidths];
startDrag(1, 500);
fireMouse('mousemove', 550);
expect(hookValue.colWidths[1]).toBe(before[1] + 50);
expect(hookValue.colWidths[2]).toBe(before[2] - 50);
});
it('dragging left shrinks left column and grows right column equally', () => {
const before = [...hookValue.colWidths];
startDrag(1, 500);
fireMouse('mousemove', 450);
expect(hookValue.colWidths[1]).toBe(before[1] - 50);
expect(hookValue.colWidths[2]).toBe(before[2] + 50);
});
it('only the two adjacent columns change', () => {
const before = [...hookValue.colWidths];
startDrag(1, 500);
fireMouse('mousemove', 550);
expect(hookValue.colWidths[0]).toBe(before[0]);
expect(hookValue.colWidths[3]).toBe(before[3]);
expect(hookValue.colWidths[4]).toBe(before[4]);
});
it('hard stop: left column clamps at minColWidth', () => {
startDrag(0, 500);
fireMouse('mousemove', 100);
expect(hookValue.colWidths[0]).toBe(MIN_COL_WIDTH);
});
it('hard stop: right neighbor absorbs exactly the full available delta', () => {
startDrag(0, 500);
fireMouse('mousemove', 100);
expect(hookValue.colWidths[1]).toBe(
DEFAULT_WIDTHS[0] + DEFAULT_WIDTHS[1] - MIN_COL_WIDTH
);
});
it('hard stop: right column clamps at minColWidth', () => {
startDrag(0, 500);
fireMouse('mousemove', 900);
expect(hookValue.colWidths[1]).toBe(MIN_COL_WIDTH);
});
it('hard stop: left neighbor absorbs exactly the full available delta', () => {
startDrag(0, 500);
fireMouse('mousemove', 900);
expect(hookValue.colWidths[0]).toBe(
DEFAULT_WIDTHS[0] + DEFAULT_WIDTHS[1] - MIN_COL_WIDTH
);
});
it('resizingIdx tracks active separator and clears on mouseup', () => {
startDrag(2, 500);
expect(hookValue.resizingIdx).toBe(2);
fireMouse('mouseup', 500);
expect(hookValue.resizingIdx).toBeNull();
});
it('stops updating after mouseup', () => {
startDrag(1, 500);
fireMouse('mousemove', 550);
const widthAfterDrag = hookValue.colWidths[1];
fireMouse('mouseup', 550);
fireMouse('mousemove', 700);
expect(hookValue.colWidths[1]).toBe(widthAfterDrag);
});
it('unmounting during an active drag does not throw', () => {
const { unmount } = setup();
startDrag(1, 500);
fireMouse('mousemove', 600);
expect(() => unmount()).not.toThrow();
});
});
});