fix: 修复 ChatPanel 无限重渲染 — useLogger 新增 bindings 参数保证引用稳定

This commit is contained in:
2026-06-02 00:50:59 +08:00
parent 29bf61f7a3
commit 628b592577
4 changed files with 151 additions and 13 deletions

View File

@@ -1,11 +1,23 @@
import { describe, expect, mock, test } from "bun:test";
import { createElement } from "react";
import { act, createElement, useState } from "react";
import type { Logger } from "../../../src/web/utils/logger";
import { useLogger } from "../../../src/web/hooks/use-logger";
import { renderWithProviders } from "../test-utils";
function BindingsHookTester({
bindings,
onMount,
}: {
bindings?: Record<string, unknown>;
onMount: (logger: Logger) => void;
}) {
const logger = useLogger(bindings);
onMount(logger);
return null;
}
function HookTester({ onMount }: { onMount: (logger: Logger) => void }) {
const logger = useLogger();
onMount(logger);
@@ -70,3 +82,109 @@ describe("useLogger", () => {
expect(errorSpy).toHaveBeenCalled();
});
});
describe("useLogger bindings 参数", () => {
test("传入 bindings 返回带上下文前缀的 Logger", () => {
const spy = mock((..._args: unknown[]) => {});
const origWarn = console.warn;
console.warn = spy;
let logger: Logger | undefined;
renderWithProviders(
createElement(BindingsHookTester, {
bindings: { component: "TestComp" },
onMount: (l: Logger) => {
logger = l;
},
}),
);
logger!.warn("绑定测试");
console.warn = origWarn;
expect(spy).toHaveBeenCalledTimes(1);
expect(spy.mock.calls[0]![0]).toMatch(/\[component=TestComp\]/);
});
test("无参调用与空 bindings 行为一致", () => {
let noBindingsLogger: Logger | undefined;
let emptyBindingsLogger: Logger | undefined;
const spy = mock((..._args: unknown[]) => {});
const origWarn = console.warn;
console.warn = spy;
renderWithProviders(
createElement(HookTester, {
onMount: (l: Logger) => {
noBindingsLogger = l;
},
}),
);
renderWithProviders(
createElement(BindingsHookTester, {
bindings: {},
onMount: (l: Logger) => {
emptyBindingsLogger = l;
},
}),
);
noBindingsLogger!.warn("a");
emptyBindingsLogger!.warn("b");
console.warn = origWarn;
expect(spy).toHaveBeenCalledTimes(2);
expect(spy.mock.calls[0]![0]).not.toMatch(/\[component=/);
expect(spy.mock.calls[1]![0]).not.toMatch(/\[component=/);
});
test("相同 bindings 值多次渲染返回同一 Logger 引用", () => {
const refs: Logger[] = [];
function StableTester() {
const logger = useLogger({ component: "Stable" });
const [, setTick] = useState(0);
refs.push(logger);
return createElement("button", {
onClick: () => setTick((t) => t + 1),
});
}
const result = renderWithProviders(createElement(StableTester));
const initialCount = refs.length;
act(() => {
result.getByRole("button").click();
});
act(() => {
result.getByRole("button").click();
});
expect(refs.length).toBeGreaterThan(initialCount);
for (let i = 1; i < refs.length; i++) {
expect(refs[i]).toBe(refs[0]);
}
});
test("bindings 值变化时返回新 Logger 引用", () => {
const refs: Logger[] = [];
let currentBindings: Record<string, unknown> = { component: "A" };
function DynamicTester() {
const logger = useLogger(currentBindings);
refs.push(logger);
return null;
}
const { rerender } = renderWithProviders(createElement(DynamicTester));
const countBefore = refs.length;
currentBindings = { component: "B" };
rerender(createElement(DynamicTester));
expect(refs.length).toBe(countBefore + 1);
expect(refs[refs.length - 1]).not.toBe(refs[0]);
});
});