This commit is contained in:
devin-ai-integration[bot] 2026-09-29 03:02:03 -04:00 • committed by GitHub
commit 4589bcfd14
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 162 additions and 68 deletions

View file

@ -902,16 +902,16 @@ describe("Teams - schema-declared metadata fields in team create", () => {
});
};
it("should prepopulate the declared key as an ordinary pair row and submit its value", async () => {
it("should show the declared key as a fixed label and submit its value under the declared key", async () => {
await openCreateModal();
fireEvent.change(screen.getByLabelText(/team name/i), { target: { value: "Test Team" } });
fireEvent.change(screen.getByTestId("create-team-models-select"), { target: { value: "gpt-4" } });
await waitFor(() => {
expect((screen.getByPlaceholderText("Key") as HTMLInputElement).value).toBe("cost_center");
});
fireEvent.change(screen.getByPlaceholderText("Value"), { target: { value: "CC-1001" } });
expect(await screen.findByTestId("metadata-schema-label")).toHaveTextContent("Cost Center");
expect(screen.queryByPlaceholderText("Key")).not.toBeInTheDocument();
expect(screen.queryByLabelText("Remove key-value pair")).not.toBeInTheDocument();
fireEvent.change(screen.getByLabelText("Cost Center"), { target: { value: "CC-1001" } });
const createTeamSubmitButtons = screen.getAllByRole("button", { name: /create team/i });
fireEvent.click(createTeamSubmitButtons[createTeamSubmitButtons.length - 1]);
@ -932,10 +932,7 @@ describe("Teams - schema-declared metadata fields in team create", () => {
fireEvent.change(screen.getByLabelText(/team name/i), { target: { value: "Test Team" } });
fireEvent.change(screen.getByTestId("create-team-models-select"), { target: { value: "gpt-4" } });
await waitFor(() => {
expect((screen.getByPlaceholderText("Key") as HTMLInputElement).value).toBe("cost_center");
});
fireEvent.change(screen.getByPlaceholderText("Value"), { target: { value: "CC-9999" } });
fireEvent.change(await screen.findByLabelText("Cost Center"), { target: { value: "CC-9999" } });
const createTeamSubmitButtons = screen.getAllByRole("button", { name: /create team/i });
fireEvent.click(createTeamSubmitButtons[createTeamSubmitButtons.length - 1]);
@ -955,16 +952,12 @@ describe("Teams - schema-declared metadata fields in team create", () => {
expect(screen.queryByRole("button", { name: /add key-value pair/i })).not.toBeInTheDocument();
});
it("should re-seed declared keys when the create modal is closed and reopened", async () => {
it("should re-seed the declared key and drop free-form rows when the create modal is closed and reopened", async () => {
await openCreateModal();
await waitFor(() => {
expect((screen.getByPlaceholderText("Key") as HTMLInputElement).value).toBe("cost_center");
});
fireEvent.click(screen.getByLabelText("Remove key-value pair"));
await waitFor(() => {
expect(screen.queryByPlaceholderText("Key")).not.toBeInTheDocument();
});
fireEvent.change(await screen.findByLabelText("Cost Center"), { target: { value: "CC-1001" } });
fireEvent.click(screen.getByRole("button", { name: /add key-value pair/i }));
fireEvent.change(await screen.findByPlaceholderText("Key"), { target: { value: "region" } });
fireEvent.click(screen.getByRole("button", { name: /^close$/i }));
await waitFor(() => {
@ -976,9 +969,8 @@ describe("Teams - schema-declared metadata fields in team create", () => {
fireEvent.click(createButton);
});
await waitFor(() => {
expect((screen.getByPlaceholderText("Key") as HTMLInputElement).value).toBe("cost_center");
});
expect(await screen.findByLabelText("Cost Center")).toHaveValue("");
expect(screen.queryByPlaceholderText("Key")).not.toBeInTheDocument();
});
});

View file

@ -212,17 +212,41 @@ describe("MetadataKeyValueFields with a declared schema", () => {
{ key: "app_name", label: "Application Name" },
];
it("should prepopulate one ordinary editable pair row per declared key", async () => {
it("should render each declared key as a fixed label with only the value editable", async () => {
render(<Harness onFinish={vi.fn()} schemaFields={schema} />);
await waitFor(() => {
expect(screen.getAllByPlaceholderText("Key").map((input) => (input as HTMLInputElement).value)).toEqual([
"cost_center",
"app_name",
expect(screen.getAllByTestId("metadata-schema-label").map((label) => label.textContent)).toEqual([
"Cost Center",
"Application Name",
]);
});
screen.getAllByPlaceholderText("Key").forEach((input) => expect(input).toBeEnabled());
expect(screen.getAllByLabelText("Remove key-value pair")).toHaveLength(2);
expect(screen.queryByPlaceholderText("Key")).not.toBeInTheDocument();
expect(screen.queryByLabelText("Remove key-value pair")).not.toBeInTheDocument();
expect(screen.getByLabelText("Cost Center")).toHaveAttribute("placeholder", "Value");
});
it("should fall back to the key when a declared field has no label", async () => {
render(<Harness onFinish={vi.fn()} schemaFields={[{ key: "cost_center" }]} />);
expect(await screen.findByTestId("metadata-schema-label")).toHaveTextContent("cost_center");
});
it("should treat an existing pair that matches a declared key as fixed too", async () => {
render(
<Harness
onFinish={vi.fn()}
schemaFields={[{ key: "cost_center", label: "Cost Center" }]}
initialMetadata={[
{ key: "cost_center", value: "CC-1001" },
{ key: "region", value: "us" },
]}
/>,
);
expect(await screen.findByLabelText("Cost Center")).toHaveValue("CC-1001");
expect(screen.getAllByPlaceholderText("Key").map((input) => (input as HTMLInputElement).value)).toEqual(["region"]);
expect(screen.getAllByLabelText("Remove key-value pair")).toHaveLength(1);
});
it("should submit a prepopulated key with its typed value", async () => {
@ -244,9 +268,9 @@ describe("MetadataKeyValueFields with a declared schema", () => {
);
await waitFor(() => {
expect(screen.getAllByPlaceholderText("Key").map((input) => (input as HTMLInputElement).value)).toEqual([
"cost_center",
"app_name",
expect(screen.getAllByTestId("metadata-schema-label").map((label) => label.textContent)).toEqual([
"Cost Center",
"Application Name",
]);
});
expect(screen.getAllByPlaceholderText("Value").map((input) => (input as HTMLInputElement).value)).toEqual([
@ -255,18 +279,54 @@ describe("MetadataKeyValueFields with a declared schema", () => {
]);
});
it("should let the user remove a prepopulated row", async () => {
it("should still let the user add and remove free-form pairs below the declared keys", async () => {
const user = userEvent.setup();
render(<Harness onFinish={vi.fn()} schemaFields={schema} />);
const onFinish = vi.fn();
render(<Harness onFinish={onFinish} schemaFields={[{ key: "cost_center", label: "Cost Center" }]} />);
await screen.findAllByPlaceholderText("Key");
await user.click(screen.getAllByLabelText("Remove key-value pair")[0]);
await screen.findByTestId("metadata-schema-label");
await user.click(screen.getByRole("button", { name: /add key-value pair/i }));
fireEvent.change(screen.getByPlaceholderText("Key"), { target: { value: "region" } });
fireEvent.change(screen.getAllByPlaceholderText("Value")[1], { target: { value: "us" } });
fireEvent.change(screen.getByLabelText("Cost Center"), { target: { value: "CC-1001" } });
await user.click(screen.getByRole("button", { name: "Save" }));
await waitFor(() => {
expect(screen.getAllByPlaceholderText("Key").map((input) => (input as HTMLInputElement).value)).toEqual([
"app_name",
]);
expect(onFinish).toHaveBeenCalledWith({
metadata: [
{ key: "cost_center", value: "CC-1001" },
{ key: "region", value: "us" },
],
});
});
await user.click(screen.getByLabelText("Remove key-value pair"));
await waitFor(() => {
expect(screen.queryByPlaceholderText("Key")).not.toBeInTheDocument();
});
expect(screen.getByTestId("metadata-schema-label")).toBeInTheDocument();
});
it("should keep duplicate keys in a free-form row", async () => {
const user = userEvent.setup();
const onFinish = vi.fn();
render(<Harness onFinish={onFinish} schemaFields={[{ key: "cost_center", label: "Cost Center" }]} />);
expect(await screen.findByTestId("metadata-schema-label")).toHaveTextContent("Cost Center");
await user.click(screen.getByRole("button", { name: /add key-value pair/i }));
fireEvent.change(screen.getByPlaceholderText("Key"), { target: { value: "cost_center" } });
expect(screen.getAllByPlaceholderText("Key")).toHaveLength(1);
expect(screen.getByPlaceholderText("Key")).toHaveValue("cost_center");
expect(screen.getAllByLabelText("Remove key-value pair")).toHaveLength(1);
expect(screen.getAllByTestId("metadata-schema-label")).toHaveLength(1);
await user.click(screen.getByRole("button", { name: "Save" }));
await waitFor(() => {
expect(screen.getByText("Duplicate key")).toBeInTheDocument();
});
expect(onFinish).not.toHaveBeenCalled();
});
it("should show a skeleton instead of the editor while the schema is loading", () => {
@ -283,9 +343,9 @@ describe("MetadataKeyValueFields with a declared schema", () => {
rerender(<Harness onFinish={onFinish} schemaFields={schema} schemaLoading={false} />);
await waitFor(() => {
expect(screen.getAllByPlaceholderText("Key").map((input) => (input as HTMLInputElement).value)).toEqual([
"cost_center",
"app_name",
expect(screen.getAllByTestId("metadata-schema-label").map((label) => label.textContent)).toEqual([
"Cost Center",
"Application Name",
]);
});
});

View file

@ -2,6 +2,7 @@ import { CircleMinus, Plus } from "lucide-react";
import React, { useEffect, useRef } from "react";
import {
useFieldArray,
useWatch,
type Control,
type FieldArrayPath,
type FieldPath,
@ -14,6 +15,7 @@ import { TeamMetadataField } from "@/app/(dashboard)/hooks/teams/useTeamMetadata
import { FormField } from "@/components/shared/form/FormField";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Skeleton } from "@/components/ui/skeleton";
export interface MetadataPair {
@ -86,7 +88,12 @@ const MetadataKeyValueFields = <TFieldValues extends FieldValues>({
schemaLoading = false,
}: MetadataKeyValueFieldsProps<TFieldValues>) => {
const { fields, append, remove } = useFieldArray({ control, name });
const watchedPairs = (useWatch({ control, name: name as unknown as FieldPath<TFieldValues> }) ?? []) as readonly (
| Partial<MetadataPair>
| undefined
)[];
const seededRef = useRef(false);
const schemaLabelsByKey = new Map(schemaFields.map((field) => [field.key, field.label || field.key]));
useEffect(() => {
if (seededRef.current || schemaLoading || schemaFields.length === 0) return;
@ -112,31 +119,64 @@ const MetadataKeyValueFields = <TFieldValues extends FieldValues>({
);
}
const getSchemaLabel = (index: number): string | undefined => {
const key = watchedPairs[index]?.key;
if (
key === undefined ||
!schemaLabelsByKey.has(key) ||
watchedPairs.findIndex((pair) => pair?.key === key) !== index
) {
return undefined;
}
return schemaLabelsByKey.get(key);
};
return (
<>
{fields.map((field, index) => (
<div key={field.id} className="mb-2 flex items-start gap-2">
<FormField control={control} name={`${name}.${index}.key` as FieldPath<TFieldValues>}>
{({ ref, value, ...rest }) => (
<Input {...rest} ref={ref} value={(value as string) ?? ""} placeholder="Key" />
{fields.map((field, index) => {
const schemaLabel = getSchemaLabel(index);
return (
<div key={field.id} className="mb-2 flex items-start gap-2">
{schemaLabel === undefined ? (
<FormField control={control} name={`${name}.${index}.key` as FieldPath<TFieldValues>}>
{({ ref, value, ...rest }) => (
<Input {...rest} ref={ref} value={(value as string) ?? ""} placeholder="Key" />
)}
</FormField>
) : (
<Label
htmlFor={`${field.id}-value`}
data-testid="metadata-schema-label"
className="h-9 w-full items-center truncate px-3 font-medium"
>
{schemaLabel}
</Label>
)}
</FormField>
<FormField control={control} name={`${name}.${index}.value` as FieldPath<TFieldValues>}>
{({ ref, value, ...rest }) => (
<Input {...rest} ref={ref} value={(value as string) ?? ""} placeholder="Value" />
<FormField control={control} name={`${name}.${index}.value` as FieldPath<TFieldValues>}>
{({ ref, value, id, ...rest }) => (
<Input
{...rest}
id={schemaLabel === undefined ? id : `${field.id}-value`}
ref={ref}
value={(value as string) ?? ""}
placeholder="Value"
/>
)}
</FormField>
{schemaLabel === undefined && (
<Button
variant="ghost"
size="icon"
aria-label="Remove key-value pair"
className="mt-1 text-destructive"
onClick={() => remove(index)}
>
<CircleMinus className="size-4" />
</Button>
)}
</FormField>
<Button
variant="ghost"
size="icon"
aria-label="Remove key-value pair"
className="mt-1 text-destructive"
onClick={() => remove(index)}
>
<CircleMinus className="size-4" />
</Button>
</div>
))}
</div>
);
})}
<Button
variant="outline"
className="w-full border-dashed"

View file

@ -1386,7 +1386,7 @@ describe("TeamInfoView", () => {
expect(screen.getByLabelText("Estimated Output Tokens Per Model")).toBeEnabled();
});
it("should keep declared keys as ordinary prefilled rows and submit the edited value", async () => {
it("should show declared keys as fixed labels and submit the edited value", async () => {
const user = userEvent.setup({ delay: null });
vi.mocked(useTeamMetadataSchema).mockReturnValue({
data: [
@ -1407,16 +1407,18 @@ describe("TeamInfoView", () => {
await openSettingsEditor(user);
await waitFor(() => {
expect(screen.getAllByPlaceholderText("Key").map((input) => (input as HTMLInputElement).value)).toEqual([
"cost_center",
"department",
"app_name",
expect(screen.getAllByTestId("metadata-schema-label").map((label) => label.textContent)).toEqual([
"Cost Center",
"Application Name",
]);
});
expect(screen.getAllByPlaceholderText("Value")[0]).toHaveValue("CC-OLD");
expect(screen.getAllByPlaceholderText("Key").map((input) => (input as HTMLInputElement).value)).toEqual([
"department",
]);
expect(screen.getByLabelText("Cost Center")).toHaveValue("CC-OLD");
await user.clear(screen.getAllByPlaceholderText("Value")[0]);
fireEvent.change(screen.getAllByPlaceholderText("Value")[0], { target: { value: "CC-NEW" } });
await user.clear(screen.getByLabelText("Cost Center"));
fireEvent.change(screen.getByLabelText("Cost Center"), { target: { value: "CC-NEW" } });
await user.click(screen.getByRole("button", { name: /save changes/i }));
await waitFor(() => {