Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions bricks/containers/src/resizable-box/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,109 @@ describe("eo-resizable-box", () => {
});
});

test("defaultSize with CSS string value", async () => {
const mockGetBoundingClientRect = jest.fn(() => ({
width: 800,
height: 600,
top: 0,
left: 0,
bottom: 600,
right: 800,
x: 0,
y: 0,
toJSON: () => ({}),
}));
jest
.spyOn(HTMLElement.prototype, "getBoundingClientRect")
.mockImplementation(mockGetBoundingClientRect as any);

const element = document.createElement("eo-resizable-box") as ResizableBox;
(element as any).defaultSize = "100vw";

act(() => {
document.body.appendChild(element);
});

expect(
(element.shadowRoot?.querySelector(".box") as HTMLElement).style.width
).toBe("800px");

act(() => {
document.body.removeChild(element);
});

(HTMLElement.prototype.getBoundingClientRect as any).mockRestore();
});
Comment on lines +119 to +151

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

将原型方法的 mock 恢复放到 finallyafterEach,避免测试污染。

Line 150 和 Line 185 的 mockRestore() 依赖测试顺利执行到末尾;一旦中途断言失败,mock 会泄漏并影响后续用例。

建议修改
 test("defaultSize with CSS string value", async () => {
-  const mockGetBoundingClientRect = jest.fn(() => ({
-    width: 800,
-    height: 600,
-    top: 0,
-    left: 0,
-    bottom: 600,
-    right: 800,
-    x: 0,
-    y: 0,
-    toJSON: () => ({}),
-  }));
-  jest
-    .spyOn(HTMLElement.prototype, "getBoundingClientRect")
-    .mockImplementation(mockGetBoundingClientRect as any);
-
-  const element = document.createElement("eo-resizable-box") as ResizableBox;
-  (element as any).defaultSize = "100vw";
-
-  act(() => {
-    document.body.appendChild(element);
-  });
-
-  expect(
-    (element.shadowRoot?.querySelector(".box") as HTMLElement).style.width
-  ).toBe("800px");
-
-  act(() => {
-    document.body.removeChild(element);
-  });
-
-  (HTMLElement.prototype.getBoundingClientRect as any).mockRestore();
+  const spy = jest
+    .spyOn(HTMLElement.prototype, "getBoundingClientRect")
+    .mockImplementation(
+      () =>
+        ({
+          width: 800,
+          height: 600,
+          top: 0,
+          left: 0,
+          bottom: 600,
+          right: 800,
+          x: 0,
+          y: 0,
+          toJSON: () => ({}),
+        }) as DOMRect
+    );
+  try {
+    const element = document.createElement("eo-resizable-box") as ResizableBox;
+    (element as any).defaultSize = "100vw";
+
+    act(() => {
+      document.body.appendChild(element);
+    });
+
+    expect(
+      (element.shadowRoot?.querySelector(".box") as HTMLElement).style.width
+    ).toBe("800px");
+
+    act(() => {
+      document.body.removeChild(element);
+    });
+  } finally {
+    spy.mockRestore();
+  }
 });

Also applies to: 153-186

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bricks/containers/src/resizable-box/index.spec.tsx` around lines 119 - 151,
The test "defaultSize with CSS string value" currently restores the prototype
mock via (HTMLElement.prototype.getBoundingClientRect as any).mockRestore() at
the end of the test which can leak if the test fails; move the restore into a
finally block or register the spy cleanup in an afterEach to guarantee
restoration. Specifically, ensure the jest.spyOn(HTMLElement.prototype,
"getBoundingClientRect") spy (mockGetBoundingClientRect) is restored
unconditionally by calling mockRestore() in a finally block inside the test or
by calling mockRestore() from a shared afterEach, and remove any direct
end-of-test restores so other tests are not affected.


test("defaultSize with CSS string value in vertical direction", async () => {
const mockGetBoundingClientRect = jest.fn(() => ({
width: 800,
height: 500,
top: 0,
left: 0,
bottom: 500,
right: 800,
x: 0,
y: 0,
toJSON: () => ({}),
}));
jest
.spyOn(HTMLElement.prototype, "getBoundingClientRect")
.mockImplementation(mockGetBoundingClientRect as any);

const element = document.createElement("eo-resizable-box") as ResizableBox;
(element as any).defaultSize = "50vh";
element.resizeDirection = "top";

act(() => {
document.body.appendChild(element);
});

expect(
(element.shadowRoot?.querySelector(".box") as HTMLElement).style.height
).toBe("500px");

act(() => {
document.body.removeChild(element);
});

(HTMLElement.prototype.getBoundingClientRect as any).mockRestore();
});

test("defaultSize with invalid CSS string falls back to default", async () => {
const element = document.createElement("eo-resizable-box") as ResizableBox;
(element as any).defaultSize = "invalid";

act(() => {
document.body.appendChild(element);
});

expect(
(element.shadowRoot?.querySelector(".box") as HTMLElement).style.width
).toBe("200px");

act(() => {
document.body.removeChild(element);
});
});

test("defaultSize with numeric string", async () => {
const element = document.createElement("eo-resizable-box") as ResizableBox;
(element as any).defaultSize = "300";

act(() => {
document.body.appendChild(element);
});

expect(
(element.shadowRoot?.querySelector(".box") as HTMLElement).style.width
).toBe("300px");

act(() => {
document.body.removeChild(element);
});
});

test("disabled", async () => {
const element = document.createElement("eo-resizable-box") as ResizableBox;
element.disabled = true;
Expand Down
45 changes: 35 additions & 10 deletions bricks/containers/src/resizable-box/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const { defineElement, property } = createDecorators();
export interface ResizableBoxProps {
resizeDirection?: ResizeDirection;
storageKey?: string;
defaultSize?: number;
defaultSize?: number | string;
minSize?: number;
minSpace?: number;
disabled?: boolean;
Expand Down Expand Up @@ -57,11 +57,11 @@ class ResizableBox extends ReactNextElement implements ResizableBoxProps {
accessor storageKey: string | undefined;

/**
* 默认尺寸(px)
* 默认尺寸,支持数字(px)或 CSS 字符串(如 "100vw"、"50%"
* @default 200
*/
@property({ type: Number })
accessor defaultSize: number | undefined;
@property()
accessor defaultSize: number | string | undefined;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* 最小尺寸(px)
Expand Down Expand Up @@ -136,6 +136,25 @@ interface ResizerStatus {
startY: number;
}

function resolveSize(
value: number | string | undefined,
direction: "horizontal" | "vertical"
): number | undefined {
if (value == null) return undefined;
if (typeof value === "number") return value;
const num = Number(value);
if (!Number.isNaN(num)) return num;
const el = document.createElement("div");
el.style.position = "absolute";
el.style.visibility = "hidden";
el.style[direction === "horizontal" ? "width" : "height"] = value;
document.body.appendChild(el);
const px =
el.getBoundingClientRect()[direction === "horizontal" ? "width" : "height"];
document.body.removeChild(el);
return px || undefined;
Comment thread
panzekun marked this conversation as resolved.
}

interface ResizableBoxComponentProps extends ResizableBoxProps {
host: HTMLElement;
}
Expand All @@ -154,7 +173,18 @@ export function ResizableBoxComponent({
host,
}: ResizableBoxComponentProps) {
const resizeDirection = _resizeDirection ?? "right";
const defaultSize = _defaultSize ?? 200;
const isVerticalDirection = useMemo(
() => ["top", "bottom"].includes(resizeDirection),
[resizeDirection]
);
const defaultSize = useMemo(
() =>
resolveSize(
_defaultSize,
isVerticalDirection ? "vertical" : "horizontal"
) ?? 200,
[_defaultSize, isVerticalDirection]
);
const minSpace = _minSpace ?? 300;
const refinedMinSize = minSize ?? defaultSize;

Expand All @@ -175,11 +205,6 @@ export function ResizableBoxComponent({
const [resized, setResized] = useState(false);
const [resizeStatus, setResizerStatus] = useState<ResizerStatus | null>(null);

const isVerticalDirection = useMemo(
() => ["top", "bottom"].includes(resizeDirection),
[resizeDirection]
);

const handleResizerMouseDown = useCallback(
(event: React.MouseEvent) => {
if (disabled) return;
Expand Down
Loading