mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-08-18 02:38:54 +00:00
* docs: design frontend performance remediation * docs: plan frontend performance remediation * test(frontend): add route asset performance budgets * perf(nginx): compress textual responses safely * perf(frontend): lazy load case study media * perf(frontend): bound static demo file tracing * perf(frontend): restore static locale boundaries * perf(frontend): defer closed workspace panels * perf(frontend): split editors and deduplicate highlighting * perf(frontend): index incremental message derivation * perf(frontend): stabilize paged history cache policy * perf(frontend): bound streaming markdown renders * perf(frontend): virtualize message history * perf(frontend): bound and virtualize chat lists * perf(frontend): suspend inactive decorative animation * perf(browser): stream latest frames as binary * perf(artifacts): stream bounded text previews * docs: finalize performance runtime boundaries * style(backend): apply test formatting * fix(frontend): keep translation functions client-side * perf(frontend): defer decorative animation bundles * test(frontend): lock optimized route budgets * fix: harden frontend performance boundaries * test(frontend): update i18n provider fixture * fix(frontend): preserve sidebar pagination position * style(backend): format artifact range test
105 lines
3.6 KiB
TypeScript
105 lines
3.6 KiB
TypeScript
import { afterEach, describe, expect, it, rs } from "@rstest/core";
|
|
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
|
|
import { HumanInputCard } from "@/components/workspace/messages/human-input-card";
|
|
import { I18nContext } from "@/core/i18n/context";
|
|
import { enUS } from "@/core/i18n/locales/en-US";
|
|
import type { HumanInputRequest } from "@/core/messages/human-input";
|
|
|
|
const formRequest: HumanInputRequest = {
|
|
version: 2,
|
|
kind: "human_input_request",
|
|
source: "ask_clarification",
|
|
request_id: "clarification:call-form",
|
|
question: "Please provide the expense details.",
|
|
input_mode: "form",
|
|
fields: [
|
|
{ name: "amount", label: "Amount", type: "number", required: true },
|
|
{ name: "category", label: "Category", type: "text", required: true },
|
|
],
|
|
};
|
|
|
|
function renderCard() {
|
|
return render(
|
|
<I18nContext.Provider
|
|
value={{ locale: "en-US", setLocale: () => undefined, t: enUS }}
|
|
>
|
|
<HumanInputCard request={formRequest} onSubmit={() => undefined} />
|
|
</I18nContext.Provider>,
|
|
);
|
|
}
|
|
|
|
afterEach(cleanup);
|
|
afterEach(() => {
|
|
rs.restoreAllMocks();
|
|
});
|
|
|
|
describe("HumanInputCard form validation (DOM)", () => {
|
|
it("keeps the error node mounted while another field is still invalid", () => {
|
|
renderCard();
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "Submit" }));
|
|
|
|
// Both required fields flagged; the error node exists and is referenced.
|
|
const amount = screen.getByLabelText(/Amount/);
|
|
const category = screen.getByLabelText(/Category/);
|
|
expect(amount.getAttribute("aria-invalid")).toBe("true");
|
|
expect(category.getAttribute("aria-invalid")).toBe("true");
|
|
const alert = screen.getByRole("alert");
|
|
expect(alert.textContent).toContain("required fields");
|
|
|
|
// Fixing ONE field must not unmount the error node the other field's
|
|
// aria-describedby still points at.
|
|
fireEvent.change(amount, { target: { value: "300" } });
|
|
expect(amount.getAttribute("aria-invalid")).toBeNull();
|
|
expect(category.getAttribute("aria-invalid")).toBe("true");
|
|
const describedBy = category.getAttribute("aria-describedby");
|
|
expect(describedBy).not.toBeNull();
|
|
expect(document.getElementById(describedBy!)).not.toBeNull();
|
|
|
|
// Fixing the last invalid field clears the error entirely.
|
|
fireEvent.change(category, { target: { value: "travel" } });
|
|
expect(screen.queryByRole("alert")).toBeNull();
|
|
});
|
|
|
|
it("keeps select fields controlled from placeholder through selection", () => {
|
|
const warnSpy = rs.spyOn(console, "warn").mockImplementation(() => ({}));
|
|
render(
|
|
<I18nContext.Provider
|
|
value={{ locale: "en-US", setLocale: () => undefined, t: enUS }}
|
|
>
|
|
<HumanInputCard
|
|
request={{
|
|
...formRequest,
|
|
fields: [
|
|
{
|
|
name: "category",
|
|
label: "Category",
|
|
type: "select",
|
|
required: true,
|
|
options: [
|
|
{ id: "category-travel", label: "travel", value: "travel" },
|
|
{ id: "category-meals", label: "meals", value: "meals" },
|
|
],
|
|
},
|
|
],
|
|
}}
|
|
onSubmit={() => undefined}
|
|
/>
|
|
</I18nContext.Provider>,
|
|
);
|
|
|
|
const trigger = screen.getByRole("combobox", {
|
|
name: "Category required",
|
|
});
|
|
fireEvent.keyDown(trigger, { key: "ArrowDown" });
|
|
fireEvent.click(screen.getByRole("option", { name: "travel" }));
|
|
|
|
expect(
|
|
warnSpy.mock.calls.some(([message]) =>
|
|
String(message).includes("uncontrolled to controlled"),
|
|
),
|
|
).toBe(false);
|
|
});
|
|
});
|