feat: Update node name and description UX editing (#5920)
* ✨ (NodeName/index.tsx): add cursor-grab class to improve user experience when dragging the node ✨ (NodeName/index.tsx): add nodrag class to prevent text selection when dragging the node ✨ (NodeStatus/index.tsx): add nodrag class to prevent text selection when dragging the node ✨ (GenericNode/index.tsx): add nopan, nodelete, nodrag, noflow classes to improve node dragging behavior * 📝 (NodeDescription/index.tsx): Update cursor style to 'auto' for better user experience 📝 (NodeName/index.tsx): Update cursor style to 'auto' for better user experience 📝 (NodeOutputfield/index.tsx): Add cursor style 'pointer' to improve interactivity * ✨ (NodeDescription/index.tsx): Add support for editing node description when selected and editNameDescription is true ✨ (NodeName/index.tsx): Add support for editing node name when selected and editNameDescription is true 🔧 (GenericNode/index.tsx): Introduce useAlternate hook to handle toggling editNameDescription state 📝 (use-alternate.tsx): Add custom hook useAlternate to handle toggling boolean state 🔧 (style/index.css): Add new CSS variable --zinc-foreground for styling purposes 🔧 (tailwind.config.mjs): Add new tailwind color variable "zinc-foreground" for styling purposes * 📝 (NodeDescription/index.tsx): Remove unnecessary setInputDescription call and update useEffect dependencies for better performance 📝 (NodeDescription/index.tsx): Update className for Textarea component to improve styling and readability 📝 (NodeDescription/index.tsx): Update className for generic-node-desc-text to improve styling and cursor behavior 📝 (NodeName/index.tsx): Remove unnecessary setInputName call and update useEffect dependencies for better performance 📝 (NodeName/index.tsx): Update className for span element to improve cursor behavior and styling 📝 (GenericNode/index.tsx): Add useRef for node element and implement useChangeOnUnfocus hook for better handling of focus events 📝 (GenericNode/index.tsx): Update className for pencil icon based on editNameDescription state for better visual feedback 📝 (GenericNode/index.tsx): Add editNameDescription to dependencies of useCallback to prevent unnecessary re-renders 📝 (GenericNode/index.tsx): Add editNameDescription to dependencies of useEffect to handle changes in editNameDescription state 📝 (use-change-on-unfocus.tsx): Implement custom hook useChangeOnUnfocus for handling focus events and state changes * Refactor NodeDescription to remove old logic and variables * Refactor NodeName component to remove unnecessary logic and variables * [autofix.ci] apply automated fixes * ✨ (NodeDescription/index.tsx): Add functionality to edit node description and handle events like blur, key down, and double click for sticky notes 📝 (NoteNode/index.tsx): Introduce useAlternate hook to toggle edit mode for node description in NoteNode component * ♻️ (NoteNode/index.tsx): refactor useAlternate hook usage to simplify code and improve readability * 🔧 (GenericNode/index.tsx): refactor className to conditionally apply translate-x styles based on showNode state for improved UI responsiveness * 📝 (NodeDescription/index.tsx): Refactor handleBlurFn and handleKeyDownFn to improve code readability and maintainability 📝 (NodeName/index.tsx): Refactor handleBlur and handleKeyDown functions for better code organization and readability 📝 (GenericNode/index.tsx): Update toggleEditNameDescription prop to setEditNameDescription for consistency and clarity 📝 (use-change-on-unfocus.tsx): Remove unnecessary handleEscape function and handleBlur event listener for better code simplicity and performance * 📝 (NodeDescription/index.tsx): Update CSS class name to use 'focus-border-primary' instead of 'focus-border-black' for consistency and clarity 📝 (GenericNode/index.tsx): Add data-testid attribute to save and edit name description buttons for testing purposes 📝 (edit-name-description-node.spec.ts): Add test to verify user can edit name and description of a node in the UI * ✨ (GenericNode/index.tsx): Add functionality to show and hide toolbar with animation based on node selection status 📝 (get-class-toolbar-transform.ts): Create helper function to determine transform classes for toolbar animation based on showToolbar and showNode status * ✨ (NodeDescription/index.tsx): add setHasChangedNodeDescription prop to update parent component when node description changes ✨ (NodeName/index.tsx): add setHasChangedNodeDescription prop to update parent component when node name changes ✨ (GenericNode/index.tsx): add hasChangedNodeDescription state and setHasChangedNodeDescription function to track changes in node description and update parent component 📝 (edit-name-description-node.spec.ts): add wait for sidebar custom component button and timeout to improve test reliability * ✨ (group.spec.ts): Update click event on "title-Group" element to improve user interaction 🐛 (group.spec.ts): Fix click event on "save-name-description-button" element to properly save changes 🐛 (general-bugs-save-changes-on-node.spec.ts): Increase timeout for selectors to prevent test failures due to slow loading 🐛 (general-bugs-save-changes-on-node.spec.ts): Fix random value generation to ensure consistent length 🐛 (general-bugs-save-changes-on-node.spec.ts): Fix click event on "add-component-button-text-output" element to add component correctly 🐛 (general-bugs-save-changes-on-node.spec.ts): Fix timeout for selector to prevent test failures due to slow loading 🐛 (general-bugs-save-changes-on-node.spec.ts): Fix verifyTextareaValue function to properly verify textarea values --------- Co-authored-by: anovazzi1 <otavio2204@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
6b17240d75
commit
8531e1b58d
14 changed files with 465 additions and 127 deletions
|
|
@ -16,6 +16,10 @@ export default function NodeDescription({
|
||||||
inputClassName,
|
inputClassName,
|
||||||
mdClassName,
|
mdClassName,
|
||||||
style,
|
style,
|
||||||
|
editNameDescription,
|
||||||
|
setEditNameDescription,
|
||||||
|
stickyNote,
|
||||||
|
setHasChangedNodeDescription,
|
||||||
}: {
|
}: {
|
||||||
description?: string;
|
description?: string;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
|
|
@ -26,8 +30,11 @@ export default function NodeDescription({
|
||||||
inputClassName?: string;
|
inputClassName?: string;
|
||||||
mdClassName?: string;
|
mdClassName?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
editNameDescription: boolean;
|
||||||
|
setEditNameDescription?: (value: boolean) => void;
|
||||||
|
stickyNote?: boolean;
|
||||||
|
setHasChangedNodeDescription?: (value: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const [inputDescription, setInputDescription] = useState(false);
|
|
||||||
const [nodeDescription, setNodeDescription] = useState<string>(
|
const [nodeDescription, setNodeDescription] = useState<string>(
|
||||||
description ?? "",
|
description ?? "",
|
||||||
);
|
);
|
||||||
|
|
@ -36,6 +43,12 @@ export default function NodeDescription({
|
||||||
const overflowRef = useRef<HTMLDivElement>(null);
|
const overflowRef = useRef<HTMLDivElement>(null);
|
||||||
const [hasScroll, sethasScroll] = useState(false);
|
const [hasScroll, sethasScroll] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selected && editNameDescription) {
|
||||||
|
takeSnapshot();
|
||||||
|
}
|
||||||
|
}, [editNameDescription]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//timeout to wait for the dom to update
|
//timeout to wait for the dom to update
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -49,13 +62,7 @@ export default function NodeDescription({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 200);
|
}, 200);
|
||||||
}, [inputDescription]);
|
}, [editNameDescription]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!selected) {
|
|
||||||
setInputDescription(false);
|
|
||||||
}
|
|
||||||
}, [selected]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNodeDescription(description ?? "");
|
setNodeDescription(description ?? "");
|
||||||
|
|
@ -80,24 +87,7 @@ export default function NodeDescription({
|
||||||
[description, emptyPlaceholder, mdClassName],
|
[description, emptyPlaceholder, mdClassName],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
const handleBlurFn = () => {
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
!inputDescription ? "overflow-auto" : "",
|
|
||||||
hasScroll ? "nowheel" : "",
|
|
||||||
charLimit ? "px-2 pb-4" : "",
|
|
||||||
"w-full",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{inputDescription ? (
|
|
||||||
<>
|
|
||||||
<Textarea
|
|
||||||
maxLength={charLimit}
|
|
||||||
className={cn("nowheel h-full", inputClassName)}
|
|
||||||
autoFocus
|
|
||||||
style={style}
|
|
||||||
onBlur={() => {
|
|
||||||
setInputDescription(false);
|
|
||||||
setNodeDescription(nodeDescription);
|
setNodeDescription(nodeDescription);
|
||||||
setNode(nodeId, (old) => ({
|
setNode(nodeId, (old) => ({
|
||||||
...old,
|
...old,
|
||||||
|
|
@ -109,13 +99,19 @@ export default function NodeDescription({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}}
|
if (stickyNote) {
|
||||||
value={nodeDescription}
|
setEditNameDescription?.(false);
|
||||||
onChange={(e) => setNodeDescription(e.target.value)}
|
}
|
||||||
onKeyDown={(e) => {
|
};
|
||||||
|
|
||||||
|
const handleKeyDownFn = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
handleKeyDown(e, nodeDescription, "");
|
handleKeyDown(e, nodeDescription, "");
|
||||||
|
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
setInputDescription(false);
|
setEditNameDescription?.(false);
|
||||||
|
setNodeDescription(description ?? "");
|
||||||
|
|
||||||
|
if (stickyNote) {
|
||||||
setNodeDescription(nodeDescription);
|
setNodeDescription(nodeDescription);
|
||||||
setNode(nodeId, (old) => ({
|
setNode(nodeId, (old) => ({
|
||||||
...old,
|
...old,
|
||||||
|
|
@ -128,7 +124,44 @@ export default function NodeDescription({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDoubleClickFn = () => {
|
||||||
|
if (stickyNote) {
|
||||||
|
setEditNameDescription?.(true);
|
||||||
|
takeSnapshot();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setHasChangedNodeDescription?.(true);
|
||||||
|
setNodeDescription(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
!editNameDescription ? "overflow-auto" : "",
|
||||||
|
hasScroll ? "nowheel" : "",
|
||||||
|
charLimit ? "px-2 pb-4" : "",
|
||||||
|
"w-full",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{editNameDescription ? (
|
||||||
|
<>
|
||||||
|
<Textarea
|
||||||
|
maxLength={charLimit}
|
||||||
|
className={cn(
|
||||||
|
"nowheel h-full w-full focus:border-primary focus:ring-0",
|
||||||
|
inputClassName,
|
||||||
|
)}
|
||||||
|
autoFocus
|
||||||
|
style={style}
|
||||||
|
onBlur={handleBlurFn}
|
||||||
|
value={nodeDescription}
|
||||||
|
onChange={onChange}
|
||||||
|
onKeyDown={handleKeyDownFn}
|
||||||
/>
|
/>
|
||||||
{charLimit && (nodeDescription?.length ?? 0) >= charLimit - 100 && (
|
{charLimit && (nodeDescription?.length ?? 0) >= charLimit - 100 && (
|
||||||
<div
|
<div
|
||||||
|
|
@ -150,14 +183,11 @@ export default function NodeDescription({
|
||||||
data-testid="generic-node-desc"
|
data-testid="generic-node-desc"
|
||||||
ref={overflowRef}
|
ref={overflowRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"nodoubleclick generic-node-desc-text h-full cursor-text text-[13px] text-muted-foreground word-break-break-word",
|
"nodoubleclick generic-node-desc-text h-full cursor-grab text-[13px] text-muted-foreground word-break-break-word",
|
||||||
description === "" || !description ? "font-light italic" : "",
|
description === "" || !description ? "font-light italic" : "",
|
||||||
placeholderClassName,
|
placeholderClassName,
|
||||||
)}
|
)}
|
||||||
onDoubleClick={(e) => {
|
onDoubleClick={handleDoubleClickFn}
|
||||||
setInputDescription(true);
|
|
||||||
takeSnapshot();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{renderedDescription}
|
{renderedDescription}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ export default function NodeName({
|
||||||
validationStatus,
|
validationStatus,
|
||||||
isOutdated,
|
isOutdated,
|
||||||
beta,
|
beta,
|
||||||
|
editNameDescription,
|
||||||
|
toggleEditNameDescription,
|
||||||
|
setHasChangedNodeDescription,
|
||||||
}: {
|
}: {
|
||||||
display_name?: string;
|
display_name?: string;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
|
|
@ -21,26 +24,25 @@ export default function NodeName({
|
||||||
validationStatus: VertexBuildTypeAPI | null;
|
validationStatus: VertexBuildTypeAPI | null;
|
||||||
isOutdated: boolean;
|
isOutdated: boolean;
|
||||||
beta: boolean;
|
beta: boolean;
|
||||||
|
editNameDescription: boolean;
|
||||||
|
toggleEditNameDescription: () => void;
|
||||||
|
setHasChangedNodeDescription: (hasChanged: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const [inputName, setInputName] = useState(false);
|
|
||||||
const [nodeName, setNodeName] = useState<string>(display_name ?? "");
|
const [nodeName, setNodeName] = useState<string>(display_name ?? "");
|
||||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
const setNode = useFlowStore((state) => state.setNode);
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selected) {
|
if (selected && editNameDescription) {
|
||||||
setInputName(false);
|
takeSnapshot();
|
||||||
}
|
}
|
||||||
}, [selected]);
|
}, [editNameDescription]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNodeName(display_name ?? "");
|
setNodeName(display_name ?? "");
|
||||||
}, [display_name]);
|
}, [display_name]);
|
||||||
|
|
||||||
return inputName ? (
|
const handleBlur = () => {
|
||||||
<div className="m-[1px] w-full">
|
|
||||||
<Input
|
|
||||||
onBlur={() => {
|
|
||||||
setInputName(false);
|
|
||||||
if (nodeName?.trim() !== "") {
|
if (nodeName?.trim() !== "") {
|
||||||
setNodeName(nodeName);
|
setNodeName(nodeName);
|
||||||
setNode(nodeId, (old) => ({
|
setNode(nodeId, (old) => ({
|
||||||
|
|
@ -56,35 +58,49 @@ export default function NodeName({
|
||||||
} else {
|
} else {
|
||||||
setNodeName(display_name ?? "");
|
setNodeName(display_name ?? "");
|
||||||
}
|
}
|
||||||
}}
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
handleBlur();
|
||||||
|
toggleEditNameDescription();
|
||||||
|
}
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
setNodeName(display_name ?? "");
|
||||||
|
toggleEditNameDescription();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setNodeName(e.target.value);
|
||||||
|
setHasChangedNodeDescription(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return editNameDescription ? (
|
||||||
|
<div className="m-[1px] w-full">
|
||||||
|
<Input
|
||||||
|
onBlur={handleBlur}
|
||||||
value={nodeName}
|
value={nodeName}
|
||||||
autoFocus
|
autoFocus
|
||||||
onChange={(e) => setNodeName(e.target.value)}
|
onChange={onChange}
|
||||||
data-testid={`input-title-${display_name}`}
|
data-testid={`input-title-${display_name}`}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
className="py-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="group flex w-full items-center gap-1">
|
<div className="group flex w-full items-center gap-1">
|
||||||
<div
|
<div
|
||||||
onDoubleClick={(event) => {
|
|
||||||
if (!showNode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setInputName(true);
|
|
||||||
takeSnapshot();
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
data-testid={"title-" + display_name}
|
data-testid={"title-" + display_name}
|
||||||
className={cn(
|
className={cn(
|
||||||
"nodoubleclick w-full truncate font-medium text-primary",
|
"nodoubleclick w-full truncate font-medium text-primary",
|
||||||
showNode ? "cursor-text" : "cursor-default",
|
showNode ? "cursor-text" : "cursor-default",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex cursor-grab items-center gap-2">
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"max-w-44 truncate text-[14px]",
|
"max-w-44 cursor-grab truncate text-[14px]",
|
||||||
validationStatus?.data?.duration && "max-w-36",
|
validationStatus?.data?.duration && "max-w-36",
|
||||||
beta && "max-w-36",
|
beta && "max-w-36",
|
||||||
validationStatus?.data?.duration && beta && "max-w-20",
|
validationStatus?.data?.duration && beta && "max-w-20",
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ const HideShowButton = memo(
|
||||||
unstyled
|
unstyled
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
data-testid={`input-inspection-${title.toLowerCase()}`}
|
data-testid={`input-inspection-${title.toLowerCase()}`}
|
||||||
|
className="cursor-pointer"
|
||||||
>
|
>
|
||||||
<ShadTooltip
|
<ShadTooltip
|
||||||
content={disabled ? null : hidden ? "Show output" : "Hide output"}
|
content={disabled ? null : hidden ? "Show output" : "Hide output"}
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,7 @@ export default function NodeStatus({
|
||||||
onClick={handleClickRun}
|
onClick={handleClickRun}
|
||||||
>
|
>
|
||||||
{showNode && (
|
{showNode && (
|
||||||
<Button unstyled className="group">
|
<Button unstyled className="nodrag group">
|
||||||
<div data-testid={`button_run_` + display_name.toLowerCase()}>
|
<div data-testid={`button_run_` + display_name.toLowerCase()}>
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name={iconName}
|
name={iconName}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||||
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||||
import { usePostValidateComponentCode } from "@/controllers/API/queries/nodes/use-post-validate-component-code";
|
import { usePostValidateComponentCode } from "@/controllers/API/queries/nodes/use-post-validate-component-code";
|
||||||
import { useUpdateNodeInternals } from "@xyflow/react";
|
import { useUpdateNodeInternals } from "@xyflow/react";
|
||||||
import { memo, useCallback, useEffect, useMemo, useState } from "react";
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useHotkeys } from "react-hotkeys-hook";
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import {
|
import {
|
||||||
|
ICON_STROKE_WIDTH,
|
||||||
TOOLTIP_HIDDEN_OUTPUTS,
|
TOOLTIP_HIDDEN_OUTPUTS,
|
||||||
TOOLTIP_OPEN_HIDDEN_OUTPUTS,
|
TOOLTIP_OPEN_HIDDEN_OUTPUTS,
|
||||||
} from "../../constants/constants";
|
} from "../../constants/constants";
|
||||||
|
|
@ -20,7 +21,9 @@ import { NodeDataType } from "../../types/flow";
|
||||||
import { checkHasToolMode } from "../../utils/reactflowUtils";
|
import { checkHasToolMode } from "../../utils/reactflowUtils";
|
||||||
import { classNames, cn } from "../../utils/utils";
|
import { classNames, cn } from "../../utils/utils";
|
||||||
|
|
||||||
|
import { useAlternate } from "@/shared/hooks/use-alternate";
|
||||||
import { useUtilityStore } from "@/stores/utilityStore";
|
import { useUtilityStore } from "@/stores/utilityStore";
|
||||||
|
import { useChangeOnUnfocus } from "../../shared/hooks/use-change-on-unfocus";
|
||||||
import { processNodeAdvancedFields } from "../helpers/process-node-advanced-fields";
|
import { processNodeAdvancedFields } from "../helpers/process-node-advanced-fields";
|
||||||
import useCheckCodeValidity from "../hooks/use-check-code-validity";
|
import useCheckCodeValidity from "../hooks/use-check-code-validity";
|
||||||
import useUpdateNodeCode from "../hooks/use-update-node-code";
|
import useUpdateNodeCode from "../hooks/use-update-node-code";
|
||||||
|
|
@ -54,8 +57,8 @@ const HiddenOutputsButton = memo(
|
||||||
>
|
>
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name={showHiddenOutputs ? "ChevronsDownUp" : "ChevronsUpDown"}
|
name={showHiddenOutputs ? "ChevronsDownUp" : "ChevronsUpDown"}
|
||||||
strokeWidth={1.5}
|
strokeWidth={ICON_STROKE_WIDTH}
|
||||||
className="h-4 w-4 text-placeholder-foreground group-hover:text-foreground"
|
className="icon-size text-placeholder-foreground group-hover:text-foreground"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
|
|
@ -99,6 +102,9 @@ function GenericNode({
|
||||||
|
|
||||||
const { mutate: validateComponentCode } = usePostValidateComponentCode();
|
const { mutate: validateComponentCode } = usePostValidateComponentCode();
|
||||||
|
|
||||||
|
const [editNameDescription, toggleEditNameDescription, set] =
|
||||||
|
useAlternate(false);
|
||||||
|
|
||||||
const updateNodeCode = useUpdateNodeCode(
|
const updateNodeCode = useUpdateNodeCode(
|
||||||
data?.id,
|
data?.id,
|
||||||
data.node!,
|
data.node!,
|
||||||
|
|
@ -199,6 +205,18 @@ function GenericNode({
|
||||||
[data.node?.outputs],
|
[data.node?.outputs],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const nodeRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useChangeOnUnfocus({
|
||||||
|
selected,
|
||||||
|
value: editNameDescription,
|
||||||
|
onChange: set,
|
||||||
|
defaultValue: false,
|
||||||
|
shouldChangeValue: (value) => value === true,
|
||||||
|
nodeRef,
|
||||||
|
callback: toggleEditNameDescription,
|
||||||
|
});
|
||||||
|
|
||||||
const renderOutputs = useCallback(
|
const renderOutputs = useCallback(
|
||||||
(outputs, key?: string) => {
|
(outputs, key?: string) => {
|
||||||
return outputs?.map((output, idx) => (
|
return outputs?.map((output, idx) => (
|
||||||
|
|
@ -231,9 +249,18 @@ function GenericNode({
|
||||||
[data.node?.outputs],
|
[data.node?.outputs],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [hasChangedNodeDescription, setHasChangedNodeDescription] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const memoizedNodeToolbarComponent = useMemo(() => {
|
const memoizedNodeToolbarComponent = useMemo(() => {
|
||||||
return selected ? (
|
return selected ? (
|
||||||
<div className={cn("absolute -top-12 left-1/2 z-50 -translate-x-1/2")}>
|
<>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute -top-12 left-1/2 z-50 -translate-x-1/2",
|
||||||
|
"transform transition-all duration-300 ease-out",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<NodeToolbarComponent
|
<NodeToolbarComponent
|
||||||
data={data}
|
data={data}
|
||||||
deleteNode={(id) => {
|
deleteNode={(id) => {
|
||||||
|
|
@ -254,6 +281,46 @@ function GenericNode({
|
||||||
isOutdated={isOutdated && isUserEdited}
|
isOutdated={isOutdated && isUserEdited}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="-z-10">
|
||||||
|
<Button
|
||||||
|
unstyled
|
||||||
|
onClick={() => {
|
||||||
|
toggleEditNameDescription();
|
||||||
|
setHasChangedNodeDescription(false);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
"nodrag absolute left-1/2 z-50 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md",
|
||||||
|
"transform transition-all duration-300 ease-out",
|
||||||
|
showNode
|
||||||
|
? "top-2 translate-x-[10.4rem]"
|
||||||
|
: "top-0 translate-x-[6.4rem]",
|
||||||
|
editNameDescription && hasChangedNodeDescription
|
||||||
|
? "bg-accent-emerald"
|
||||||
|
: "bg-zinc-foreground",
|
||||||
|
)}
|
||||||
|
data-testid={
|
||||||
|
editNameDescription && hasChangedNodeDescription
|
||||||
|
? "save-name-description-button"
|
||||||
|
: "edit-name-description-button"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ForwardedIconComponent
|
||||||
|
name={
|
||||||
|
editNameDescription && hasChangedNodeDescription
|
||||||
|
? "Check"
|
||||||
|
: "PencilLine"
|
||||||
|
}
|
||||||
|
strokeWidth={ICON_STROKE_WIDTH}
|
||||||
|
className={cn(
|
||||||
|
editNameDescription && hasChangedNodeDescription
|
||||||
|
? "text-accent-emerald-foreground"
|
||||||
|
: "text-muted-foreground",
|
||||||
|
"icon-size",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
);
|
);
|
||||||
|
|
@ -268,6 +335,9 @@ function GenericNode({
|
||||||
isUserEdited,
|
isUserEdited,
|
||||||
selected,
|
selected,
|
||||||
shortcuts,
|
shortcuts,
|
||||||
|
editNameDescription,
|
||||||
|
hasChangedNodeDescription,
|
||||||
|
toggleEditNameDescription,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -298,6 +368,9 @@ function GenericNode({
|
||||||
validationStatus={validationStatus}
|
validationStatus={validationStatus}
|
||||||
isOutdated={isOutdated}
|
isOutdated={isOutdated}
|
||||||
beta={data.node?.beta || false}
|
beta={data.node?.beta || false}
|
||||||
|
editNameDescription={editNameDescription}
|
||||||
|
toggleEditNameDescription={toggleEditNameDescription}
|
||||||
|
setHasChangedNodeDescription={setHasChangedNodeDescription}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
|
|
@ -308,6 +381,9 @@ function GenericNode({
|
||||||
validationStatus,
|
validationStatus,
|
||||||
isOutdated,
|
isOutdated,
|
||||||
data.node?.beta,
|
data.node?.beta,
|
||||||
|
editNameDescription,
|
||||||
|
toggleEditNameDescription,
|
||||||
|
setHasChangedNodeDescription,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const renderNodeStatus = useCallback(() => {
|
const renderNodeStatus = useCallback(() => {
|
||||||
|
|
@ -346,9 +422,19 @@ function GenericNode({
|
||||||
mdClassName={"dark:prose-invert"}
|
mdClassName={"dark:prose-invert"}
|
||||||
nodeId={data.id}
|
nodeId={data.id}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
|
editNameDescription={editNameDescription}
|
||||||
|
setEditNameDescription={set}
|
||||||
|
setHasChangedNodeDescription={setHasChangedNodeDescription}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [data.node?.description, data.id, selected]);
|
}, [
|
||||||
|
data.node?.description,
|
||||||
|
data.id,
|
||||||
|
selected,
|
||||||
|
editNameDescription,
|
||||||
|
toggleEditNameDescription,
|
||||||
|
setHasChangedNodeDescription,
|
||||||
|
]);
|
||||||
|
|
||||||
const renderInputParameters = useCallback(() => {
|
const renderInputParameters = useCallback(() => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -382,8 +468,8 @@ function GenericNode({
|
||||||
<div className="flex h-10 w-full items-center gap-4 rounded-t-[0.69rem] bg-warning p-2 px-4 text-warning-foreground">
|
<div className="flex h-10 w-full items-center gap-4 rounded-t-[0.69rem] bg-warning p-2 px-4 text-warning-foreground">
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name="AlertTriangle"
|
name="AlertTriangle"
|
||||||
strokeWidth={1.5}
|
strokeWidth={ICON_STROKE_WIDTH}
|
||||||
className="h-[18px] w-[18px] shrink-0"
|
className="icon-size shrink-0"
|
||||||
/>
|
/>
|
||||||
<span className="flex-1 truncate text-sm font-medium">
|
<span className="flex-1 truncate text-sm font-medium">
|
||||||
{showNode && "Update Ready"}
|
{showNode && "Update Ready"}
|
||||||
|
|
@ -438,7 +524,7 @@ function GenericNode({
|
||||||
{showNode && <div>{renderDescription()}</div>}
|
{showNode && <div>{renderDescription()}</div>}
|
||||||
</div>
|
</div>
|
||||||
{showNode && (
|
{showNode && (
|
||||||
<div className="relative">
|
<div className="nopan nodelete nodrag noflow relative cursor-auto">
|
||||||
<>
|
<>
|
||||||
{renderInputParameters()}
|
{renderInputParameters()}
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
NOTE_NODE_MIN_HEIGHT,
|
NOTE_NODE_MIN_HEIGHT,
|
||||||
NOTE_NODE_MIN_WIDTH,
|
NOTE_NODE_MIN_WIDTH,
|
||||||
} from "@/constants/constants";
|
} from "@/constants/constants";
|
||||||
|
import { useAlternate } from "@/shared/hooks/use-alternate";
|
||||||
import { NoteDataType } from "@/types/flow";
|
import { NoteDataType } from "@/types/flow";
|
||||||
import { cn } from "@/utils/utils";
|
import { cn } from "@/utils/utils";
|
||||||
import { NodeResizer } from "@xyflow/react";
|
import { NodeResizer } from "@xyflow/react";
|
||||||
|
|
@ -32,6 +33,8 @@ function NoteNode({
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const [editNameDescription, set] = useAlternate(false);
|
||||||
|
|
||||||
const MemoNoteToolbarComponent = useMemo(
|
const MemoNoteToolbarComponent = useMemo(
|
||||||
() =>
|
() =>
|
||||||
selected ? (
|
selected ? (
|
||||||
|
|
@ -98,6 +101,9 @@ function NoteNode({
|
||||||
placeholderClassName={
|
placeholderClassName={
|
||||||
COLOR_OPTIONS[bgColor] === null ? "" : "dark:!text-background"
|
COLOR_OPTIONS[bgColor] === null ? "" : "dark:!text-background"
|
||||||
}
|
}
|
||||||
|
editNameDescription={editNameDescription}
|
||||||
|
setEditNameDescription={set}
|
||||||
|
stickyNote
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export const getTransformClasses = (showToolbar, showNode) => {
|
||||||
|
if (showToolbar && showNode) return "translate-x-[10.4rem] opacity-100";
|
||||||
|
if (!showToolbar && showNode) return "translate-x-[8rem] opacity-0";
|
||||||
|
if (showToolbar && !showNode) return "translate-x-[6.4rem] opacity-100";
|
||||||
|
return "translate-x-[5rem] opacity-0";
|
||||||
|
};
|
||||||
14
src/frontend/src/shared/hooks/use-alternate.tsx
Normal file
14
src/frontend/src/shared/hooks/use-alternate.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
|
export const useAlternate = (
|
||||||
|
initialState: boolean = false,
|
||||||
|
): [boolean, () => void, (value: boolean) => void] => {
|
||||||
|
const [switched, setSwitched] = useState(initialState);
|
||||||
|
const set = useCallback((value) => setSwitched(value), []);
|
||||||
|
|
||||||
|
const alternate = useCallback(
|
||||||
|
() => setSwitched((prevState) => !prevState),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
return [switched, alternate, set];
|
||||||
|
};
|
||||||
49
src/frontend/src/shared/hooks/use-change-on-unfocus.tsx
Normal file
49
src/frontend/src/shared/hooks/use-change-on-unfocus.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { RefObject, useEffect } from "react";
|
||||||
|
|
||||||
|
interface UseChangeOnUnfocusProps<T> {
|
||||||
|
selected?: boolean;
|
||||||
|
value: T;
|
||||||
|
onChange?: (value: T) => void;
|
||||||
|
defaultValue: T;
|
||||||
|
shouldChangeValue?: (value: T) => boolean;
|
||||||
|
nodeRef: RefObject<HTMLDivElement>;
|
||||||
|
callback?: () => void;
|
||||||
|
callbackEscape?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useChangeOnUnfocus<T>({
|
||||||
|
selected,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
defaultValue,
|
||||||
|
shouldChangeValue,
|
||||||
|
nodeRef,
|
||||||
|
callback,
|
||||||
|
}: UseChangeOnUnfocusProps<T>) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selected) {
|
||||||
|
onChange?.(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVisibilityChange = () => {
|
||||||
|
if (document.hidden && shouldChangeValue?.(value)) {
|
||||||
|
onChange?.(defaultValue);
|
||||||
|
callback?.();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
selected,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
defaultValue,
|
||||||
|
shouldChangeValue,
|
||||||
|
nodeRef,
|
||||||
|
callback,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
@ -167,6 +167,8 @@
|
||||||
--tool-mode-gradient-2: #ff3276;
|
--tool-mode-gradient-2: #ff3276;
|
||||||
|
|
||||||
--slider-input-border: #d4d4d8;
|
--slider-input-border: #d4d4d8;
|
||||||
|
|
||||||
|
--zinc-foreground: 240 5.9% 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
|
|
@ -323,5 +325,7 @@
|
||||||
--node-ring: 240 6% 90%;
|
--node-ring: 240 6% 90%;
|
||||||
|
|
||||||
--slider-input-border: #d4d4d8;
|
--slider-input-border: #d4d4d8;
|
||||||
|
|
||||||
|
--zinc-foreground: 240 5.2% 33.9%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -257,6 +257,7 @@ const config = {
|
||||||
"terminal-green": "hsl(var(--terminal-green))",
|
"terminal-green": "hsl(var(--terminal-green))",
|
||||||
"cosmic-void": "hsl(var(--cosmic-void))",
|
"cosmic-void": "hsl(var(--cosmic-void))",
|
||||||
"slider-input-border": "var(--slider-input-border)",
|
"slider-input-border": "var(--slider-input-border)",
|
||||||
|
"zinc-foreground": "hsl(var(--zinc-foreground))",
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: `var(--radius)`,
|
lg: `var(--radius)`,
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,10 @@ test.describe("group node test", () => {
|
||||||
await page.getByTestId("title-OpenAI").click({ modifiers: ["Control"] });
|
await page.getByTestId("title-OpenAI").click({ modifiers: ["Control"] });
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Group" }).click();
|
await page.getByRole("button", { name: "Group" }).click();
|
||||||
await page.getByTestId("title-Group").dblclick();
|
await page.getByTestId("title-Group").click();
|
||||||
|
await page.getByTestId("edit-name-description-button").click();
|
||||||
await page.getByTestId("input-title-Group").first().fill("test");
|
await page.getByTestId("input-title-Group").first().fill("test");
|
||||||
await page.getByTestId("icon-Ungroup").first().click();
|
await page.getByTestId("save-name-description-button").first().click();
|
||||||
await page.keyboard.press("Control+g");
|
await page.keyboard.press("Control+g");
|
||||||
await page.getByTestId("title-OpenAI").isVisible();
|
await page.getByTestId("title-OpenAI").isVisible();
|
||||||
await page.getByTestId("title-Prompt").isVisible();
|
await page.getByTestId("title-Prompt").isVisible();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||||
|
|
||||||
|
test(
|
||||||
|
"user should be able to edit name and description of a node",
|
||||||
|
{ tag: ["@release", "@workspace"] },
|
||||||
|
|
||||||
|
async ({ page }) => {
|
||||||
|
const randomName = Math.random().toString(36).substring(2, 15);
|
||||||
|
const randomDescription = Math.random().toString(36).substring(2, 15);
|
||||||
|
|
||||||
|
const randomName_2 = Math.random().toString(36).substring(2, 15);
|
||||||
|
const randomDescription_2 = Math.random().toString(36).substring(2, 15);
|
||||||
|
|
||||||
|
const randomName_3 = Math.random().toString(36).substring(2, 15);
|
||||||
|
const randomDescription_3 = Math.random().toString(36).substring(2, 15);
|
||||||
|
|
||||||
|
const randomName_4 = Math.random().toString(36).substring(2, 15);
|
||||||
|
const randomDescription_4 = Math.random().toString(36).substring(2, 15);
|
||||||
|
|
||||||
|
await awaitBootstrapTest(page);
|
||||||
|
|
||||||
|
await page.waitForSelector('[data-testid="blank-flow"]', {
|
||||||
|
timeout: 30000,
|
||||||
|
});
|
||||||
|
await page.getByTestId("blank-flow").click();
|
||||||
|
|
||||||
|
await page.waitForSelector(
|
||||||
|
'[data-testid="sidebar-custom-component-button"]',
|
||||||
|
{
|
||||||
|
timeout: 30000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
|
await page.getByTestId("sidebar-custom-component-button").click();
|
||||||
|
|
||||||
|
await page.getByTestId("div-generic-node").click();
|
||||||
|
|
||||||
|
await page.getByTestId("edit-name-description-button").click();
|
||||||
|
|
||||||
|
await page.getByTestId("input-title-Custom Component").fill(randomName);
|
||||||
|
|
||||||
|
await page.getByTestId("textarea").fill(randomDescription);
|
||||||
|
|
||||||
|
await page.getByTestId("api_button_modal").click();
|
||||||
|
|
||||||
|
await page.getByText("Close").last().click();
|
||||||
|
|
||||||
|
expect(await page.getByText(randomName).count()).toBe(1);
|
||||||
|
expect(await page.getByText(randomDescription).count()).toBe(1);
|
||||||
|
|
||||||
|
await page.getByTestId("div-generic-node").click();
|
||||||
|
|
||||||
|
await page.getByTestId("edit-name-description-button").click();
|
||||||
|
|
||||||
|
await page.getByTestId(`input-title-${randomName}`).fill(randomName_2);
|
||||||
|
|
||||||
|
await page.getByTestId("textarea").fill(randomDescription_2);
|
||||||
|
|
||||||
|
await page.getByTestId("save-name-description-button").click();
|
||||||
|
|
||||||
|
expect(await page.getByText(randomName_2).count()).toBe(1);
|
||||||
|
expect(await page.getByText(randomDescription_2).count()).toBe(1);
|
||||||
|
|
||||||
|
await page.getByTestId("div-generic-node").click();
|
||||||
|
|
||||||
|
await page.getByTestId("edit-name-description-button").click();
|
||||||
|
|
||||||
|
await page.getByTestId(`input-title-${randomName_2}`).fill(randomName_3);
|
||||||
|
|
||||||
|
await page.keyboard.press("Enter");
|
||||||
|
|
||||||
|
expect(await page.getByText(randomName_3).count()).toBe(1);
|
||||||
|
|
||||||
|
await page.getByTestId("div-generic-node").click();
|
||||||
|
|
||||||
|
await page.getByTestId("edit-name-description-button").click();
|
||||||
|
|
||||||
|
await page.getByTestId(`input-title-${randomName_3}`).fill(randomName_4);
|
||||||
|
|
||||||
|
await page.getByTestId("textarea").fill(randomDescription_4);
|
||||||
|
|
||||||
|
await page.keyboard.press("Escape");
|
||||||
|
|
||||||
|
expect(await page.getByText(randomName_4).count()).toBe(1);
|
||||||
|
|
||||||
|
expect(await page.getByText(randomDescription_2).count()).toBe(1);
|
||||||
|
|
||||||
|
expect(await page.getByText(randomDescription_4).count()).toBe(0);
|
||||||
|
|
||||||
|
expect(await page.getByText(randomName_3).count()).toBe(0);
|
||||||
|
|
||||||
|
await page.getByTestId("div-generic-node").click();
|
||||||
|
|
||||||
|
await page.getByTestId("edit-name-description-button").click();
|
||||||
|
|
||||||
|
await page.getByTestId("textarea").fill(randomDescription_3);
|
||||||
|
|
||||||
|
await page.getByTestId(`input-title-${randomName_4}`).fill(randomName_3);
|
||||||
|
|
||||||
|
await page.keyboard.press("Escape");
|
||||||
|
|
||||||
|
expect(await page.getByText(randomDescription_3).count()).toBe(1);
|
||||||
|
|
||||||
|
expect(await page.getByText(randomName_4).count()).toBe(1);
|
||||||
|
|
||||||
|
expect(await page.getByText(randomName_3).count()).toBe(0);
|
||||||
|
|
||||||
|
expect(await page.getByText(randomDescription_4).count()).toBe(0);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
@ -2,17 +2,25 @@ import { expect, Page, test } from "@playwright/test";
|
||||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||||
|
|
||||||
async function verifyTextareaValue(page: Page, value: string) {
|
async function verifyTextareaValue(page: Page, value: string) {
|
||||||
|
await page
|
||||||
|
.getByTestId("textarea_str_input_value")
|
||||||
|
.waitFor({ state: "visible" });
|
||||||
await page.getByTestId("textarea_str_input_value").fill(value);
|
await page.getByTestId("textarea_str_input_value").fill(value);
|
||||||
|
|
||||||
|
await expect(page.getByTestId("textarea_str_input_value")).toHaveValue(value);
|
||||||
|
|
||||||
await page.getByTestId("icon-ChevronLeft").first().click();
|
await page.getByTestId("icon-ChevronLeft").first().click();
|
||||||
|
|
||||||
await page.waitForSelector('[data-testid="list-card"]', {
|
await page.waitForSelector('[data-testid="list-card"]', {
|
||||||
timeout: 3000,
|
timeout: 5000,
|
||||||
|
state: "visible",
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByTestId("list-card").first().click();
|
await page.getByTestId("list-card").first().click();
|
||||||
|
|
||||||
await page.waitForSelector('[data-testid="textarea_str_input_value"]', {
|
await page.waitForSelector('[data-testid="textarea_str_input_value"]', {
|
||||||
timeout: 3000,
|
timeout: 5000,
|
||||||
|
state: "visible",
|
||||||
});
|
});
|
||||||
|
|
||||||
const inputValue = await page
|
const inputValue = await page
|
||||||
|
|
@ -26,32 +34,35 @@ test(
|
||||||
{ tag: ["@release", "@components"] },
|
{ tag: ["@release", "@components"] },
|
||||||
async ({ page }) => {
|
async ({ page }) => {
|
||||||
const randomValues = Array.from({ length: 4 }, () =>
|
const randomValues = Array.from({ length: 4 }, () =>
|
||||||
Math.random().toString(36).substring(2, 15),
|
Math.random().toString(36).substring(2, 8),
|
||||||
);
|
);
|
||||||
|
|
||||||
await awaitBootstrapTest(page);
|
await awaitBootstrapTest(page);
|
||||||
await page.getByTestId("blank-flow").click();
|
await page.getByTestId("blank-flow").click();
|
||||||
|
|
||||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||||
timeout: 100000,
|
timeout: 10000,
|
||||||
|
state: "visible",
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByTestId("sidebar-search-input").click();
|
await page.getByTestId("sidebar-search-input").click();
|
||||||
await page.getByTestId("sidebar-search-input").fill("text output");
|
await page.getByTestId("sidebar-search-input").fill("text output");
|
||||||
|
|
||||||
await page
|
await page.getByTestId("outputsText Output").waitFor({ state: "visible" });
|
||||||
.getByTestId("outputsText Output")
|
|
||||||
.hover()
|
|
||||||
.then(async () => {
|
|
||||||
await page.getByTestId("add-component-button-text-output").click();
|
await page.getByTestId("add-component-button-text-output").click();
|
||||||
});
|
|
||||||
|
|
||||||
await page.waitForSelector('[data-testid="title-Text Output"]', {
|
await page.waitForSelector('[data-testid="title-Text Output"]', {
|
||||||
timeout: 3000,
|
timeout: 5000,
|
||||||
|
state: "visible",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify each random value
|
|
||||||
for (const value of randomValues) {
|
for (const value of randomValues) {
|
||||||
|
try {
|
||||||
await verifyTextareaValue(page, value);
|
await verifyTextareaValue(page, value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to verify value: ${value}`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue