feat: Revamp GlobalVariableModal (#5512)

* 📝 (GlobalVariableModal.tsx): Refactor GlobalVariableModal component to use Tabs component instead of Select for type selection and improve layout and styling of input fields and labels
🔧 (index.tsx): Add popoverWidth prop to InputComponent to allow setting the width of the popover in CustomInputPopover component
🔧 (index.tsx): Add popoverWidth prop to InputComponent in InputGlobalComponent to set the width of the popover to 315px

✨ (tabs-button.tsx): introduce new TabsButton component to handle tab functionality in UI components
📝 (tabs-button.tsx): add documentation for Tabs, TabsContent, TabsList, TabsTrigger components
📝 (components/index.ts): add popoverWidth property to InputComponentType to control the width of the popover in UI components

* ✨ (GlobalVariableModal.tsx): Add ForwardedIconComponent to display an icon next to the modal header text for better visual representation
📝 (GlobalVariableModal.tsx): Update TabsTrigger components to include data-testid attribute for testing purposes

✨ (index.tsx): Introduce new components OptionBadge, CommandItemContent, and SelectionIndicator to improve code organization and reusability
♻️ (index.tsx): Refactor handleRemoveOption function to have a more descriptive parameter signature and improve readability
📝 (index.tsx): Add comments to clarify the purpose of handleOptionSelect function and improve code documentation
📝 (index.tsx): Add comments to describe the purpose of getInputClassName and getAnchorClassName functions for better code understanding

✨ (globalVariables.spec.ts): improve placeholder text for variable name and value fields for better user understanding and experience

* 🐛 (GlobalVariableModal.tsx): Fix disabled state logic for TabsTrigger components to correctly reflect initialData type
📝 (GlobalVariableModal.tsx): Update label for submit button to dynamically change based on the presence of initialData

* 🐛 (index.tsx): Fix issue where options were not being correctly memoized as a Set to prevent unnecessary re-renders. Update memoizedOptions to correctly memoize options as a Set.

* 📝 (userSettings.spec.ts): Update placeholder text for variable name and value fields to improve clarity and user experience.
This commit is contained in:
Cristhian Zanforlin Lousa 2025-01-07 09:07:08 -03:00 • committed by GitHub
commit af4fb3774e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 327 additions and 194 deletions

View file

@ -7,17 +7,10 @@ import getUnavailableFields from "@/stores/globalVariablesStore/utils/get-unavai
import { GlobalVariable } from "@/types/global_variables"; import { GlobalVariable } from "@/types/global_variables";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import ForwardedIconComponent from "@/components/common/genericIconComponent"; import { ForwardedIconComponent } from "@/components/common/genericIconComponent";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs-button";
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import BaseModal from "@/modals/baseModal"; import BaseModal from "@/modals/baseModal";
import useAlertStore from "@/stores/alertStore"; import useAlertStore from "@/stores/alertStore";
import { useTypesStore } from "@/stores/typesStore"; import { useTypesStore } from "@/stores/typesStore";
@ -134,88 +127,89 @@ export default function GlobalVariableModal({
onSubmit={submitForm} onSubmit={submitForm}
disable={disabled} disable={disabled}
> >
<BaseModal.Header <BaseModal.Header description="This variable will be available for use across your flows.">
description={
"This variable will be available for you to use in any of your projects."
}
>
<span className="pr-2">
{" "}
{initialData ? "Update" : "Create"} Variable{" "}
</span>
<ForwardedIconComponent <ForwardedIconComponent
name="Globe" name="Globe"
className="h-6 w-6 pl-1 text-primary" className="h-6 w-6 pr-1 text-primary"
aria-hidden="true" aria-hidden="true"
/> />
{initialData ? "Update Variable" : "Create Variable"}
</BaseModal.Header> </BaseModal.Header>
<BaseModal.Trigger disable={disabled} asChild={asChild}> <BaseModal.Trigger disable={disabled} asChild={asChild}>
{children} {children}
</BaseModal.Trigger> </BaseModal.Trigger>
<BaseModal.Content> <BaseModal.Content>
<div className="flex h-full w-full flex-col gap-4 align-middle"> <div className="flex h-full w-full flex-col gap-4">
<Label>Variable Name</Label> <div className="space-y-2">
<Label>Type*</Label>
<Tabs
defaultValue={type}
onValueChange={setType}
className="w-full"
>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger
disabled={!!initialData?.type}
data-testid="credential-tab"
value="Credential"
>
Credential
</TabsTrigger>
<TabsTrigger
disabled={!!initialData?.type}
data-testid="generic-tab"
value="Generic"
>
Generic
</TabsTrigger>
</TabsList>
</Tabs>
</div>
<div className="space-y-2">
<Label>Name*</Label>
<Input <Input
value={key} value={key}
onChange={(e) => { onChange={(e) => setKey(e.target.value)}
setKey(e.target.value); placeholder="Enter a name for the variable..."
}} />
placeholder="Insert a name for the variable..." </div>
></Input>
<Label>Type (optional)</Label>
<Select <div className="space-y-2">
disabled={disabled} <Label>Value*</Label>
onValueChange={setType}
value={type}
defaultValue={type}
>
<SelectTrigger
className="h-full w-full"
data-testid="select-type-global-variables"
>
<SelectValue placeholder="Choose a type for the variable..." />
</SelectTrigger>
<SelectContent id="type-global-variables">
{["Generic", "Credential"].map((option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
<Label>Value</Label>
{type === "Credential" ? ( {type === "Credential" ? (
<InputComponent <InputComponent
password password
value={value} value={value}
onChange={(e) => { onChange={(e) => setValue(e)}
setValue(e); placeholder="Enter a value for the variable..."
}}
placeholder="Insert a value for the variable..."
nodeStyle nodeStyle
/> />
) : ( ) : (
<Textarea <Input
value={value} value={value}
onChange={(e) => { onChange={(e) => setValue(e.target.value)}
setValue(e.target.value); placeholder="Enter a value for the variable..."
}}
placeholder="Insert a value for the variable..."
className="w-full resize-none custom-scroll"
/> />
)} )}
</div>
<Label>Apply To Fields (optional)</Label> <div className="space-y-2">
<Label>Apply to fields</Label>
<InputComponent <InputComponent
setSelectedOptions={(value) => setFields(value)} setSelectedOptions={(value) => setFields(value)}
selectedOptions={fields} selectedOptions={fields}
options={availableFields} options={availableFields}
password={false} password={false}
placeholder="Choose a field for the variable..." placeholder="Choose a field for the variable..."
id={"apply-to-fields"} id="apply-to-fields"
></InputComponent> popoverWidth="520px"
optionsPlaceholder="Fields"
/>
<div className="text-xs text-muted-foreground">
Selected fields will auto-apply the variable as a default value.
</div>
</div>
</div> </div>
</BaseModal.Content> </BaseModal.Content>
<BaseModal.Footer <BaseModal.Footer

View file

@ -15,7 +15,129 @@ import {
import { cn } from "@/utils/utils"; import { cn } from "@/utils/utils";
import { PopoverAnchor } from "@radix-ui/react-popover"; import { PopoverAnchor } from "@radix-ui/react-popover";
import { X } from "lucide-react"; import { X } from "lucide-react";
import { useState } from "react"; import { ReactNode, useMemo, useState } from "react";
const OptionBadge = ({
option,
onRemove,
variant = "emerald",
className = "",
}: {
option: string;
variant?:
| "default"
| "emerald"
| "gray"
| "secondary"
| "destructive"
| "outline"
| "secondaryStatic"
| "pinkStatic"
| "successStatic"
| "errorStatic";
className?: string;
onRemove: (e: React.MouseEvent<HTMLButtonElement>) => void;
}) => (
<Badge
variant={
variant as
| "default"
| "emerald"
| "gray"
| "secondary"
| "destructive"
| "outline"
| "secondaryStatic"
| "pinkStatic"
| "successStatic"
| "errorStatic"
}
className={cn("flex items-center gap-1 truncate", className)}
>
<div className="truncate">{option}</div>
<X
className="h-3 w-3 cursor-pointer bg-transparent hover:text-destructive"
onClick={(e) =>
onRemove(e as unknown as React.MouseEvent<HTMLButtonElement>)
}
data-testid="remove-icon-badge"
/>
</Badge>
);
const CommandItemContent = ({
option,
isSelected,
optionButton,
}: {
option: string;
isSelected: boolean;
optionButton: (option: string) => ReactNode;
}) => (
<div className="group flex w-full items-center justify-between">
<div className="flex items-center justify-between">
<SelectionIndicator isSelected={isSelected} />
<span className="max-w-52 truncate pr-2">{option}</span>
</div>
{optionButton && optionButton(option)}
</div>
);
const SelectionIndicator = ({ isSelected }: { isSelected: boolean }) => (
<div
className={cn(
"relative mr-2 h-4 w-4",
isSelected ? "opacity-100" : "opacity-0",
)}
>
<div className="absolute opacity-100 transition-all group-hover:opacity-0">
<ForwardedIconComponent
name="Check"
className="mr-2 h-4 w-4 text-primary"
aria-hidden="true"
/>
</div>
<div className="absolute opacity-0 transition-all group-hover:opacity-100">
<ForwardedIconComponent
name="X"
className="mr-2 h-4 w-4 text-status-red"
aria-hidden="true"
/>
</div>
</div>
);
const getInputClassName = (
editNode: boolean,
disabled: boolean,
password: boolean,
selectedOptions: string[],
) => {
return cn(
"popover-input nodrag w-full truncate px-1 pr-4",
editNode && "px-2",
editNode && disabled && "h-fit w-fit",
disabled &&
"disabled:text-muted disabled:opacity-100 placeholder:disabled:text-muted-foreground",
password && "text-clip pr-14",
selectedOptions?.length >= 0 && "cursor-default",
);
};
const getAnchorClassName = (
editNode: boolean,
disabled: boolean,
isFocused: boolean,
) => {
return cn(
"primary-input noflow nopan nodelete nodrag border-1 flex h-full min-h-[2.375rem] cursor-default flex-wrap items-center px-2",
editNode && "min-h-7 p-0 px-1",
editNode && disabled && "min-h-5 border-muted",
disabled && "bg-muted text-muted",
isFocused &&
"border-foreground ring-1 ring-foreground hover:border-foreground",
);
};
const CustomInputPopover = ({ const CustomInputPopover = ({
id, id,
@ -43,16 +165,20 @@ const CustomInputPopover = ({
nodeStyle, nodeStyle,
optionButton, optionButton,
autoFocus, autoFocus,
className, popoverWidth,
}) => { }) => {
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
const memoizedOptions = useMemo(() => new Set<string>(options), [options]);
const PopoverContentInput = editNode const PopoverContentInput = editNode
? PopoverContent ? PopoverContent
: PopoverContentWithoutPortal; : PopoverContentWithoutPortal;
const handleRemoveOption = (optionToRemove, e) => { const handleRemoveOption = (
e.stopPropagation(); // Prevent the popover from opening when removing badges optionToRemove: string,
e: React.MouseEvent<HTMLButtonElement>,
) => {
e.stopPropagation();
if (setSelectedOptions) { if (setSelectedOptions) {
setSelectedOptions( setSelectedOptions(
selectedOptions.filter((option) => option !== optionToRemove), selectedOptions.filter((option) => option !== optionToRemove),
@ -62,58 +188,52 @@ const CustomInputPopover = ({
} }
}; };
const handleOptionSelect = (currentValue: string) => {
if (setSelectedOption) {
setSelectedOption(currentValue === selectedOption ? "" : currentValue);
}
if (setSelectedOptions) {
setSelectedOptions(
selectedOptions?.includes(currentValue)
? selectedOptions.filter((item) => item !== currentValue)
: [...(selectedOptions || []), currentValue],
);
}
!setSelectedOptions && setShowOptions(false);
};
return ( return (
<Popover modal open={showOptions} onOpenChange={setShowOptions}> <Popover modal open={showOptions} onOpenChange={setShowOptions}>
<PopoverAnchor> <PopoverAnchor>
<div <div
data-testid={`anchor-${id}`} data-testid={`anchor-${id}`}
className={cn( className={getAnchorClassName(editNode, disabled, isFocused)}
"primary-input noflow nopan nodelete nodrag border-1 flex h-full min-h-[2.375rem] cursor-default flex-wrap items-center px-2", onClick={() => !nodeStyle && !disabled && setShowOptions(true)}
editNode && "min-h-7 p-0 px-1",
editNode && disabled && "min-h-5 border-muted",
disabled && "bg-muted text-muted",
isFocused &&
"border-foreground ring-1 ring-foreground hover:border-foreground",
)}
onClick={() => {
if (!nodeStyle && !disabled) {
setShowOptions(true);
}
}}
> >
{selectedOptions?.length > 0 ? ( {selectedOptions?.length > 0 ? (
selectedOptions.map((option) => ( <div className="mr-5 flex flex-wrap gap-2">
<Badge {selectedOptions.map((option) => (
<OptionBadge
key={option} key={option}
variant="secondary" option={option}
className="m-[1px] flex items-center gap-1 truncate px-1" onRemove={(e) => handleRemoveOption(option, e)}
> className="rounded-[3px] p-1 font-mono"
<div className="truncate">{option}</div>
<X
className="h-3 w-3 cursor-pointer bg-transparent hover:text-destructive"
onClick={(e) => handleRemoveOption(option, e)}
/> />
</Badge> ))}
)) </div>
) : selectedOption?.length > 0 ? ( ) : selectedOption?.length > 0 ? (
<Badge <OptionBadge
option={selectedOption}
onRemove={(e) => handleRemoveOption(selectedOption, e)}
variant={nodeStyle ? "emerald" : "secondary"} variant={nodeStyle ? "emerald" : "secondary"}
className={cn( className={cn(
"flex items-center gap-1 truncate",
editNode && "text-xs", editNode && "text-xs",
nodeStyle ? "font-jetbrains rounded-[3px] px-1" : "bg-muted", nodeStyle ? "rounded-[3px] px-1 font-mono" : "bg-muted",
)} )}
>
<div className="max-w-36 truncate">{selectedOption}</div>
<X
className="h-3 w-3 cursor-pointer bg-transparent hover:text-destructive"
onClick={(e) => handleRemoveOption(selectedOption, e)}
data-testid={"remove-icon-badge"}
/> />
</Badge>
) : null} ) : null}
{!selectedOption && ( {!selectedOption?.length && !selectedOptions?.length && (
<input <input
autoComplete="off" autoComplete="off"
onFocus={() => setIsFocused(true)} onFocus={() => setIsFocused(true)}
@ -128,13 +248,11 @@ const CustomInputPopover = ({
value={value || ""} value={value || ""}
disabled={disabled} disabled={disabled}
required={required} required={required}
className={cn( className={getInputClassName(
"popover-input nodrag w-full truncate px-1 pr-4", editNode,
editNode && "px-2", disabled,
editNode && disabled && "h-fit w-fit", password,
disabled && selectedOptions,
"disabled:text-muted disabled:opacity-100 placeholder:disabled:text-muted-foreground",
password && "text-clip pr-14",
)} )}
placeholder={ placeholder={
selectedOptions?.length > 0 || selectedOption ? "" : placeholder selectedOptions?.length > 0 || selectedOption ? "" : placeholder
@ -149,9 +267,13 @@ const CustomInputPopover = ({
)} )}
</div> </div>
</PopoverAnchor> </PopoverAnchor>
<PopoverContentInput <PopoverContentInput
className="noflow nowheel nopan nodelete nodrag p-0" className="noflow nowheel nopan nodelete nodrag p-0"
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }} style={{
minWidth: refInput?.current?.clientWidth ?? "200px",
width: popoverWidth ?? null,
}}
side="bottom" side="bottom"
align="start" align="start"
> >
@ -168,59 +290,21 @@ const CustomInputPopover = ({
<CommandInput placeholder={optionsPlaceholder} /> <CommandInput placeholder={optionsPlaceholder} />
<CommandList> <CommandList>
<CommandGroup> <CommandGroup>
{options.map((option, id) => ( {Array.from(memoizedOptions).map((option, id) => (
<CommandItem <CommandItem
key={option + id} key={option + id}
value={option} value={option}
onSelect={(currentValue) => { onSelect={handleOptionSelect}
if (setSelectedOption) {
setSelectedOption(
currentValue === selectedOption ? "" : currentValue,
);
}
if (setSelectedOptions) {
setSelectedOptions(
selectedOptions?.includes(currentValue)
? selectedOptions.filter(
(item) => item !== currentValue,
)
: [...(selectedOptions || []), currentValue],
);
}
!setSelectedOptions && setShowOptions(false);
}}
className="group" className="group"
> >
<div className="group flex w-full items-center justify-between"> <CommandItemContent
<div className="flex items-center justify-between"> option={option}
<div isSelected={
className={cn(
"relative mr-2 h-4 w-4",
selectedOption === option || selectedOption === option ||
selectedOptions?.includes(option) selectedOptions?.includes(option)
? "opacity-100" }
: "opacity-0", optionButton={optionButton}
)}
>
<div className="absolute opacity-100 transition-all group-hover:opacity-0">
<ForwardedIconComponent
name="Check"
className="mr-2 h-4 w-4 text-primary"
aria-hidden="true"
/> />
</div>
<div className="absolute opacity-0 transition-all group-hover:opacity-100">
<ForwardedIconComponent
name="X"
className="mr-2 h-4 w-4 text-status-red"
aria-hidden="true"
/>
</div>
</div>
<span className="max-w-52 truncate pr-2">{option}</span>
</div>
{optionButton && optionButton(option)}
</div>
</CommandItem> </CommandItem>
))} ))}
{optionsButton} {optionsButton}

View file

@ -39,6 +39,7 @@ export default function InputComponent({
onChangeFolderName, onChangeFolderName,
nodeStyle, nodeStyle,
isToolMode, isToolMode,
popoverWidth,
}: InputComponentType): JSX.Element { }: InputComponentType): JSX.Element {
const [pwdVisible, setPwdVisible] = useState(false); const [pwdVisible, setPwdVisible] = useState(false);
const refInput = useRef<HTMLInputElement>(null); const refInput = useRef<HTMLInputElement>(null);
@ -148,8 +149,8 @@ export default function InputComponent({
blurOnEnter={blurOnEnter} blurOnEnter={blurOnEnter}
options={options} options={options}
optionsPlaceholder={optionsPlaceholder} optionsPlaceholder={optionsPlaceholder}
className={className}
nodeStyle={nodeStyle} nodeStyle={nodeStyle}
popoverWidth={popoverWidth}
/> />
)} )}
</> </>

View file

@ -90,6 +90,7 @@ export default function InputGlobalComponent({
return ( return (
<InputComponent <InputComponent
nodeStyle nodeStyle
popoverWidth="315px"
placeholder={getPlaceholder(disabled, placeholder)} placeholder={getPlaceholder(disabled, placeholder)}
id={id} id={id}
editNode={editNode} editNode={editNode}

View file

@ -0,0 +1,54 @@
"use client";
import { cn } from "@/utils/utils";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import * as React from "react";
const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className,
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className,
)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className,
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsContent, TabsList, TabsTrigger };

View file

@ -46,6 +46,7 @@ export type InputComponentType = {
onChangeFolderName?: (e: any) => void; onChangeFolderName?: (e: any) => void;
nodeStyle?: boolean; nodeStyle?: boolean;
isToolMode?: boolean; isToolMode?: boolean;
popoverWidth?: string;
}; };
export type DropDownComponent = { export type DropDownComponent = {
disabled?: boolean; disabled?: boolean;

View file

@ -33,11 +33,11 @@ test(
await page.getByTestId("icon-Globe").nth(0).click(); await page.getByTestId("icon-Globe").nth(0).click();
await page.getByText("Add New Variable", { exact: true }).click(); await page.getByText("Add New Variable", { exact: true }).click();
await page await page
.getByPlaceholder("Insert a name for the variable...") .getByPlaceholder("Enter a name for the variable...")
.fill(genericName); .fill(genericName);
await page.getByText("Generic", { exact: true }).first().isVisible(); await page.getByText("Generic", { exact: true }).first().isVisible();
await page await page
.getByPlaceholder("Insert a value for the variable...") .getByPlaceholder("Enter a value for the variable...")
.fill("This is a test of generic variable value"); .fill("This is a test of generic variable value");
await page.getByText("Save Variable", { exact: true }).click(); await page.getByText("Save Variable", { exact: true }).click();
expect(page.getByText(genericName, { exact: true })).not.toBeNull(); expect(page.getByText(genericName, { exact: true })).not.toBeNull();
@ -45,12 +45,11 @@ test(
await page.getByText("Add New Variable", { exact: true }).click(); await page.getByText("Add New Variable", { exact: true }).click();
await page await page
.getByPlaceholder("Insert a name for the variable...") .getByPlaceholder("Enter a name for the variable...")
.fill(credentialName); .fill(credentialName);
await page.getByTestId("select-type-global-variables").first().click(); await page.getByTestId("credential-tab").click();
await page.getByText("Credential", { exact: true }).last().click();
await page await page
.getByPlaceholder("Insert a value for the variable...") .getByPlaceholder("Enter a value for the variable...")
.fill("This is a test of credential variable value"); .fill("This is a test of credential variable value");
await page.getByText("Save Variable", { exact: true }).click(); await page.getByText("Save Variable", { exact: true }).click();
expect(page.getByText(credentialName, { exact: true })).not.toBeNull(); expect(page.getByText(credentialName, { exact: true })).not.toBeNull();

View file

@ -44,40 +44,39 @@ test(
.isVisible(); .isVisible();
await page.getByText("Add New").click(); await page.getByText("Add New").click();
await page await page
.getByPlaceholder("Insert a name for the variable...") .getByPlaceholder("Enter a name for the variable...")
.fill(randomName); .fill(randomName);
await page.getByTestId("select-type-global-variables").first().click();
await page.getByText("Generic", { exact: true }).last().isVisible(); await page.getByText("Generic", { exact: true }).last().isVisible();
await page.getByText("Generic", { exact: true }).last().click(); await page.getByText("Generic", { exact: true }).last().click();
await page await page
.getByPlaceholder("Insert a value for the variable...") .getByPlaceholder("Enter a value for the variable...")
.fill("testtesttesttesttesttesttesttest"); .fill("testtesttesttesttesttesttesttest");
await page.getByTestId("popover-anchor-apply-to-fields").click(); await page.getByTestId("popover-anchor-apply-to-fields").click();
await page.getByPlaceholder("Search options...").waitFor({ await page.getByPlaceholder("Fields").waitFor({
state: "visible", state: "visible",
timeout: 30000, timeout: 30000,
}); });
await page.getByPlaceholder("Search options...").fill("System"); await page.getByPlaceholder("Fields").fill("System");
await page.waitForSelector("text=System", { timeout: 30000 }); await page.waitForSelector("text=System", { timeout: 30000 });
await page.getByText("System").last().click(); await page.getByText("System").last().click();
await page.getByPlaceholder("Search options...").fill("openAI"); await page.getByPlaceholder("Fields").fill("openAI");
await page.waitForSelector("text=openai", { timeout: 30000 }); await page.waitForSelector("text=openai", { timeout: 30000 });
await page.getByText("openai").last().click(); await page.getByText("openai").last().click();
await page.getByPlaceholder("Search options...").waitFor({ await page.getByPlaceholder("Fields").waitFor({
state: "visible", state: "visible",
timeout: 30000, timeout: 30000,
}); });
await page.getByPlaceholder("Search options...").fill("ollama"); await page.getByPlaceholder("Fields").fill("ollama");
await page.keyboard.press("Escape"); await page.keyboard.press("Escape");
await page.getByText("Save Variable", { exact: true }).click(); await page.getByText("Save Variable", { exact: true }).click();
@ -87,13 +86,13 @@ test(
await page.getByText(randomName).last().click(); await page.getByText(randomName).last().click();
await page.getByText(randomName).last().click(); await page.getByText(randomName).last().click();
await page.getByPlaceholder("Insert a name for the variable...").waitFor({ await page.getByPlaceholder("Enter a name for the variable...").waitFor({
state: "visible", state: "visible",
timeout: 30000, timeout: 30000,
}); });
await page await page
.getByPlaceholder("Insert a name for the variable...") .getByPlaceholder("Enter a name for the variable...")
.fill(randomName2); .fill(randomName2);
await page.getByText("Update Variable", { exact: true }).last().click(); await page.getByText("Update Variable", { exact: true }).last().click();
@ -102,13 +101,13 @@ test(
await page.getByText(randomName2).last().click(); await page.getByText(randomName2).last().click();
await page.getByPlaceholder("Insert a name for the variable...").waitFor({ await page.getByPlaceholder("Enter a name for the variable...").waitFor({
state: "visible", state: "visible",
timeout: 30000, timeout: 30000,
}); });
await page await page
.getByPlaceholder("Insert a name for the variable...") .getByPlaceholder("Enter a name for the variable...")
.fill(randomName3); .fill(randomName3);
await page.getByText("Update Variable", { exact: true }).last().click(); await page.getByText("Update Variable", { exact: true }).last().click();