mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-24 08:57:31 +08:00
chore: Upgrade to React 19.2.3 with compatibility fixes (#11100)
* feat(dependencies): add langchain-mcp-adapters to avoid incompatibility with langchain-core<1.0.0 * upgrade react 19 * fix icons not rendering * test fixes * fix shard 10 * new lock * test(lock-flow.spec.ts): update edge deletion tests to use Backspace instead of Delete for consistency and reliability in edge removal * [autofix.ci] apply automated fixes * chore(pyproject.toml): remove langchain-mcp-adapters dependency to avoid incompatibility issues with langchain-core<1.0.0 * uv lock * test(regression): increase wait times in general bugs reset flow tests to ensure stability and reliability of test execution --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
32f43e2018
commit
f787c6715f
4006
src/frontend/package-lock.json
generated
4006
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,24 +4,27 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@chakra-ui/number-input": "^2.1.2",
|
||||
"@headlessui/react": "^2.0.4",
|
||||
"@chakra-ui/system": "^2.6.2",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@headlessui/react": "^2.2.9",
|
||||
"@hookform/resolvers": "^3.6.0",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.1.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-form": "^0.0.3",
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-form": "^0.1.8",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-radio-group": "^1.3.7",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slider": "^1.2.1",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-radio-group": "^1.3.8",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slider": "^1.3.6",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tabler/icons-react": "^3.6.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
@ -29,14 +32,14 @@
|
||||
"@types/axios": "^0.14.0",
|
||||
"@xyflow/react": "^12.3.6",
|
||||
"ace-builds": "^1.41.0",
|
||||
"ag-grid-community": "^32.0.2",
|
||||
"ag-grid-react": "^32.0.2",
|
||||
"ag-grid-community": "^32.3.9",
|
||||
"ag-grid-react": "^32.3.9",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"axios": "^1.7.4",
|
||||
"base64-js": "^1.5.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"cmdk": "^1.1.1",
|
||||
"dompurify": "^3.2.4",
|
||||
"dotenv": "^16.4.5",
|
||||
"elkjs": "^0.9.3",
|
||||
@ -56,24 +59,23 @@
|
||||
"pako": "^2.1.0",
|
||||
"playwright": "^1.56.0",
|
||||
"pretty-ms": "^9.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-ace": "^11.0.1",
|
||||
"react": "^19.2.1",
|
||||
"react-ace": "^14.0.1",
|
||||
"react-cookie": "^7.1.4",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dom": "^19.2.1",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-hook-form": "^7.52.0",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-laag": "^2.0.5",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-markdown": "^9.1.0",
|
||||
"react-pdf": "^9.0.0",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-sortablejs": "^6.1.4",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"reactflow": "^11.11.3",
|
||||
"rehype-mathjax": "^4.0.3",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"remark-gfm": "3.0.1",
|
||||
"rehype-mathjax": "^6.0.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"shadcn-ui": "^0.9.4",
|
||||
"short-unique-id": "^5.2.0",
|
||||
@ -127,23 +129,24 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.1.1",
|
||||
"@jest/types": "^30.0.1",
|
||||
"@modelcontextprotocol/server-everything": "^0.6.2",
|
||||
"@playwright/test": "^1.56.0",
|
||||
"@storybook/addon-docs": "^10.1.0",
|
||||
"@storybook/addon-links": "^10.1.0",
|
||||
"@storybook/react": "^10.1.0",
|
||||
"@storybook/react-vite": "^10.1.0",
|
||||
"@modelcontextprotocol/server-everything": "^0.6.2",
|
||||
"@playwright/test": "^1.56.0",
|
||||
"@swc/cli": "^0.5.2",
|
||||
"@swc/core": "^1.6.1",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@testing-library/jest-dom": "^6.4.6",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.1",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash": "4.17.5",
|
||||
"@types/node": "^20.14.2",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
|
||||
@ -76,12 +76,18 @@ export default function NodeDescription({
|
||||
}
|
||||
return (
|
||||
<MemoizedMarkdown
|
||||
linkTarget="_blank"
|
||||
className={cn(
|
||||
"markdown prose flex w-full flex-col leading-5 word-break-break-word [&_pre]:whitespace-break-spaces [&_pre]:!bg-code-description-background [&_pre_code]:!bg-code-description-background",
|
||||
stickyNote ? "text-mmd" : "text-xs",
|
||||
mdClassName,
|
||||
)}
|
||||
components={{
|
||||
a: ({ node, ...props }) => (
|
||||
<a {...props} target="_blank" rel="noopener noreferrer">
|
||||
{props.children}
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{String(description)}
|
||||
</MemoizedMarkdown>
|
||||
|
||||
@ -30,13 +30,12 @@ export default function SingleAlert({
|
||||
{dropItem.list.map((item, idx) => (
|
||||
<li className="word-break-break-word" key={idx}>
|
||||
<Markdown
|
||||
linkTarget="_blank"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
className="align-text-top"
|
||||
components={{
|
||||
a: ({ node, ...props }) => (
|
||||
<a
|
||||
href={props.href}
|
||||
{...props}
|
||||
target="_blank"
|
||||
className="underline"
|
||||
rel="noopener noreferrer"
|
||||
|
||||
@ -61,13 +61,12 @@ export default function ErrorAlert({
|
||||
<li key={index} className="word-break-break-word">
|
||||
<span className="">
|
||||
<Markdown
|
||||
linkTarget="_blank"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
className="align-text-top"
|
||||
components={{
|
||||
a: ({ node, ...props }) => (
|
||||
<a
|
||||
href={props.href}
|
||||
{...props}
|
||||
target="_blank"
|
||||
className="underline"
|
||||
rel="noopener noreferrer"
|
||||
|
||||
@ -106,10 +106,26 @@ export const ForwardedIconComponent = memo(
|
||||
<div className={className}></div>
|
||||
);
|
||||
|
||||
// Check if TargetIcon is a valid React component (function, class, or lazy component)
|
||||
// In React 19, lazy components have $$typeof Symbol, and forwardRef components have render property
|
||||
const isValidComponent =
|
||||
typeof TargetIcon === "function" ||
|
||||
(typeof TargetIcon === "object" &&
|
||||
TargetIcon !== null &&
|
||||
// Check for various React component types:
|
||||
// - $$typeof: lazy, forwardRef, memo components (Symbol.for('react.lazy'), etc.)
|
||||
// - render: forwardRef components in some React versions
|
||||
// - _payload: lazy component internals
|
||||
// - type: wrapped components (memo wrapping forwardRef)
|
||||
(TargetIcon.$$typeof ||
|
||||
TargetIcon.render ||
|
||||
TargetIcon._payload ||
|
||||
TargetIcon.type));
|
||||
|
||||
return (
|
||||
<Suspense fallback={skipFallback ? undefined : fallback}>
|
||||
<ErrorBoundary onError={handleError}>
|
||||
{TargetIcon?.render || TargetIcon?._payload ? (
|
||||
{isValidComponent ? (
|
||||
<TargetIcon
|
||||
className={className}
|
||||
style={style}
|
||||
@ -146,15 +162,30 @@ export const ForwardedIconComponent = memo(
|
||||
);
|
||||
|
||||
// Simple error boundary component for catching lazy load errors
|
||||
class ErrorBoundary extends React.Component<{
|
||||
children: React.ReactNode;
|
||||
onError: () => void;
|
||||
}> {
|
||||
componentDidCatch(error: any) {
|
||||
class ErrorBoundary extends React.Component<
|
||||
{
|
||||
children: React.ReactNode;
|
||||
onError: () => void;
|
||||
},
|
||||
{ hasError: boolean }
|
||||
> {
|
||||
constructor(props: { children: React.ReactNode; onError: () => void }) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(_error: Error) {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error) {
|
||||
this.props.onError();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return null;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,7 +171,6 @@ export function ContentBlockDisplay({
|
||||
<Markdown
|
||||
className="text-sm font-semibold text-foreground"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
linkTarget="_blank"
|
||||
rehypePlugins={[rehypeMathjax]}
|
||||
components={{
|
||||
p({ node, ...props }) {
|
||||
|
||||
@ -55,10 +55,14 @@ export default function ContentDisplay({
|
||||
<div className="ml-1 pr-20">
|
||||
<Markdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
linkTarget="_blank"
|
||||
rehypePlugins={[rehypeMathjax]}
|
||||
className="markdown prose max-w-full text-sm font-normal dark:prose-invert"
|
||||
components={{
|
||||
a: ({ node, ...props }) => (
|
||||
<a {...props} target="_blank" rel="noopener noreferrer">
|
||||
{props.children}
|
||||
</a>
|
||||
),
|
||||
p({ node, ...props }) {
|
||||
return (
|
||||
<span className="block w-fit max-w-full">
|
||||
@ -69,7 +73,8 @@ export default function ContentDisplay({
|
||||
pre({ node, ...props }) {
|
||||
return <>{props.children}</>;
|
||||
},
|
||||
code: ({ node, inline, className, children, ...props }) => {
|
||||
code: ({ node, className, children, ...props }) => {
|
||||
const inline = !(props as any).hasOwnProperty("data-language");
|
||||
let content = children as string;
|
||||
if (
|
||||
Array.isArray(children) &&
|
||||
@ -165,7 +170,10 @@ export default function ContentDisplay({
|
||||
ul({ node, ...props }) {
|
||||
return <ul className="max-w-full">{props.children}</ul>;
|
||||
},
|
||||
code: ({ node, inline, className, children, ...props }) => {
|
||||
code: ({ node, className, children, ...props }) => {
|
||||
const inline = !(props as any).hasOwnProperty(
|
||||
"data-language",
|
||||
);
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
return !inline ? (
|
||||
<SimplifiedCodeTabComponent
|
||||
|
||||
@ -141,11 +141,14 @@ const DisclosureTrigger = memo(function DisclosureTrigger({
|
||||
{React.Children.map(children, (child) => {
|
||||
if (!React.isValidElement(child)) return child;
|
||||
|
||||
return React.cloneElement(child, {
|
||||
const childElement = child as React.ReactElement<{
|
||||
className?: string;
|
||||
}>;
|
||||
return React.cloneElement(childElement, {
|
||||
...childProps,
|
||||
className: cn(className, child.props.className),
|
||||
...child.props,
|
||||
});
|
||||
className: cn(className, childElement.props.className),
|
||||
...childElement.props,
|
||||
} as React.Attributes);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -35,7 +35,7 @@ export function TextLoop({
|
||||
}: TextLoopProps) {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const items = Children.toArray(children);
|
||||
const timerRef = useRef<NodeJS.Timeout>();
|
||||
const timerRef = useRef<NodeJS.Timeout>(undefined);
|
||||
|
||||
const updateIndex = useCallback(() => {
|
||||
setCurrentIndex((current) => {
|
||||
|
||||
@ -2,8 +2,9 @@ import type React from "react";
|
||||
import { forwardRef } from "react";
|
||||
import { AIMLComponent } from "./AI-ML";
|
||||
|
||||
export const AIMLIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
|
||||
(props, ref) => {
|
||||
return <AIMLComponent ref={ref} {...props} />;
|
||||
},
|
||||
);
|
||||
export const AIMLIcon = forwardRef<
|
||||
SVGSVGElement,
|
||||
React.PropsWithChildren<{ className?: string }>
|
||||
>((props, ref) => {
|
||||
return <AIMLComponent ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@ import { AthenaComponent } from "./athena";
|
||||
|
||||
export const AthenaIcon = forwardRef<
|
||||
SVGSVGElement,
|
||||
React.PropsWithChildren<{}>
|
||||
React.PropsWithChildren<{ className?: string }>
|
||||
>((props, ref) => {
|
||||
return <AthenaComponent ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
@ -107,11 +107,12 @@ import { UpstashSvgIcon } from "@/icons/Upstash";
|
||||
import { VectaraIcon } from "@/icons/VectaraIcon";
|
||||
import { VertexAIIcon } from "@/icons/VertexAI";
|
||||
import { VllmIcon } from "@/icons/vLLM";
|
||||
import { VLMRunIcon } from "@/icons/VLMRun";
|
||||
import { WeaviateIcon } from "@/icons/Weaviate";
|
||||
import { WikipediaIcon } from "@/icons/Wikipedia";
|
||||
import { WolframIcon } from "@/icons/Wolfram";
|
||||
import { XAIIcon } from "@/icons/xAI";
|
||||
import { YouTubeSvgIcon as YouTubeIcon } from "@/icons/Youtube";
|
||||
import { YoutubeIcon as YouTubeIcon } from "@/icons/Youtube";
|
||||
import { ZepMemoryIcon } from "@/icons/ZepMemory";
|
||||
import { JigsawStackIcon } from "./JigsawStack";
|
||||
import { WindsurfIcon } from "./Windsurf";
|
||||
|
||||
@ -2,11 +2,9 @@ import type React from "react";
|
||||
import { forwardRef } from "react";
|
||||
import SvgFreezeAll from "./freezeAll";
|
||||
|
||||
("./freezeAll.jsx");
|
||||
|
||||
export const freezeAllIcon = forwardRef<
|
||||
SVGSVGElement,
|
||||
React.PropsWithChildren<{}>
|
||||
React.PropsWithChildren<{ className?: string }>
|
||||
>((props, ref) => {
|
||||
return <SvgFreezeAll ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
@ -96,12 +96,11 @@ export const ErrorView = ({
|
||||
{content.reason && (
|
||||
<span className="">
|
||||
<Markdown
|
||||
linkTarget="_blank"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
a: ({ node, ...props }) => (
|
||||
<a
|
||||
href={props.href}
|
||||
{...props}
|
||||
target="_blank"
|
||||
className="underline"
|
||||
rel="noopener noreferrer"
|
||||
@ -118,11 +117,13 @@ export const ErrorView = ({
|
||||
},
|
||||
code: ({
|
||||
node,
|
||||
inline,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const inline = !(
|
||||
props as any
|
||||
).hasOwnProperty("data-language");
|
||||
let content = children as string;
|
||||
if (
|
||||
Array.isArray(children) &&
|
||||
|
||||
@ -29,7 +29,6 @@ export const MarkdownField = ({
|
||||
<div className="w-full items-baseline gap-2">
|
||||
<Markdown
|
||||
remarkPlugins={[remarkGfm as any]}
|
||||
linkTarget="_blank"
|
||||
rehypePlugins={[rehypeMathjax, rehypeRaw]}
|
||||
className={cn(
|
||||
"markdown prose flex w-full max-w-full flex-col items-baseline text-sm font-normal word-break-break-word dark:prose-invert",
|
||||
@ -67,7 +66,8 @@ export const MarkdownField = ({
|
||||
</div>
|
||||
);
|
||||
},
|
||||
code: ({ node, inline, className, children, ...props }) => {
|
||||
code: ({ node, className, children, ...props }) => {
|
||||
const inline = !(props as any).hasOwnProperty("data-language");
|
||||
let content = children as string;
|
||||
if (
|
||||
Array.isArray(children) &&
|
||||
|
||||
@ -49,6 +49,7 @@ export default function ToolsTable({
|
||||
const applyingSelection = useRef<boolean>(false);
|
||||
const previousRowsCount = useRef<number>(0);
|
||||
const skipSelectionReapply = useRef<number>(0);
|
||||
const [isGridReady, setIsGridReady] = useState(false);
|
||||
|
||||
const { setOpen: setSidebarOpen } = useSidebar();
|
||||
|
||||
@ -59,7 +60,10 @@ export default function ToolsTable({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
if (!open) {
|
||||
setIsGridReady(false);
|
||||
return;
|
||||
}
|
||||
previousRowsCount.current = rows.length;
|
||||
const initialData = cloneDeep(rows).map((row, index) => ({
|
||||
...row,
|
||||
@ -93,7 +97,7 @@ export default function ToolsTable({
|
||||
}, [rows]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!agGrid.current?.api || !selectedRows || !open) return;
|
||||
if (!agGrid.current?.api || !selectedRows || !open || !isGridReady) return;
|
||||
|
||||
// Don't re-apply selection if we're just editing data fields (slug/description)
|
||||
if (skipSelectionReapply.current > 0) {
|
||||
@ -116,7 +120,7 @@ export default function ToolsTable({
|
||||
setTimeout(() => {
|
||||
applyingSelection.current = false;
|
||||
}, 50);
|
||||
}, [selectedRows, open]);
|
||||
}, [selectedRows, open, isGridReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
@ -315,6 +319,10 @@ export default function ToolsTable({
|
||||
setSidebarOpen(false);
|
||||
};
|
||||
|
||||
const handleGridReady = () => {
|
||||
setIsGridReady(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="flex h-full w-full flex-1 flex-col gap-2 overflow-hidden py-4">
|
||||
@ -344,6 +352,7 @@ export default function ToolsTable({
|
||||
getRowId={getRowId}
|
||||
pagination={true}
|
||||
paginationPageSize={50}
|
||||
onGridReady={handleGridReady}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@ -252,13 +252,12 @@ export default function FlowBuildingComponent() {
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<Markdown
|
||||
linkTarget="_blank"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
className="my-1.5 align-text-top truncate-doubleline"
|
||||
components={{
|
||||
a: ({ node, ...props }) => (
|
||||
<a
|
||||
href={props.href}
|
||||
{...props}
|
||||
target="_blank"
|
||||
className="underline"
|
||||
rel="noopener noreferrer"
|
||||
|
||||
@ -22,7 +22,7 @@ export type SingleAlertComponentType = {
|
||||
};
|
||||
export type AlertDropdownType = {
|
||||
children: JSX.Element;
|
||||
notificationRef?: React.RefObject<HTMLDivElement>;
|
||||
notificationRef?: React.RefObject<HTMLDivElement | null>;
|
||||
onClose?: () => void;
|
||||
};
|
||||
export type AlertItemType = {
|
||||
|
||||
17
src/frontend/src/vite-env.d.ts
vendored
17
src/frontend/src/vite-env.d.ts
vendored
@ -1,6 +1,23 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-svgr/client" />
|
||||
|
||||
// React 19 compatibility - JSX namespace is now exported from React
|
||||
import type { JSX } from "react";
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface Element extends React.JSX.Element {}
|
||||
interface ElementClass extends React.JSX.ElementClass {}
|
||||
interface ElementAttributesProperty
|
||||
extends React.JSX.ElementAttributesProperty {}
|
||||
interface ElementChildrenAttribute
|
||||
extends React.JSX.ElementChildrenAttribute {}
|
||||
interface IntrinsicAttributes extends React.JSX.IntrinsicAttributes {}
|
||||
interface IntrinsicClassAttributes<T>
|
||||
extends React.JSX.IntrinsicClassAttributes<T> {}
|
||||
interface IntrinsicElements extends React.JSX.IntrinsicElements {}
|
||||
}
|
||||
}
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly BACKEND_URL: string;
|
||||
readonly ACCESS_TOKEN_EXPIRE_SECONDS: string;
|
||||
|
||||
@ -43,11 +43,15 @@ test(
|
||||
timeout: 20000,
|
||||
});
|
||||
|
||||
await page.waitForSelector("text=OAUTH2", { timeout: 20000 });
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.getByTestId("button_open_list_selection").click();
|
||||
|
||||
await page.getByTestId(`list_item_fetch_emails`).click();
|
||||
|
||||
await page.getByTestId("int_int_max_results").fill("10");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.getByTestId("button_run_gmail").click();
|
||||
|
||||
@ -55,9 +59,11 @@ test(
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByTestId("output-inspection-dataframe-gmailapi").click();
|
||||
await page
|
||||
.getByTestId("output-inspection-dataframe-composiogmailapicomponent")
|
||||
.click();
|
||||
|
||||
const colNumber: number = await page.getByRole("gridcell").count();
|
||||
expect(colNumber).toBeGreaterThan(9);
|
||||
expect(colNumber).toBeGreaterThan(1);
|
||||
},
|
||||
);
|
||||
|
||||
@ -136,29 +136,81 @@ test(
|
||||
await page.getByLabel("Message logs").click();
|
||||
await page.getByText("Page 1 of 1", { exact: true }).click();
|
||||
// check rename session
|
||||
await page.getByRole("combobox").click();
|
||||
await page.getByLabel("Rename").getByText("Rename").click();
|
||||
await page.getByRole("textbox").fill("new name");
|
||||
await page.mouse.move(0, 0);
|
||||
await page.getByTestId("session-selector").first().hover();
|
||||
await page
|
||||
.getByTestId("session-selector")
|
||||
.first()
|
||||
.getByTestId("icon-MoreHorizontal")
|
||||
.click();
|
||||
await page.getByLabel("Rename").click();
|
||||
await page
|
||||
.getByTestId("session-selector")
|
||||
.first()
|
||||
.getByRole("textbox")
|
||||
.fill("new name");
|
||||
await page.getByTestId("icon-Check").click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.getByTestId("session-selector").getByText("new name").click();
|
||||
await page
|
||||
.getByTestId("session-selector")
|
||||
.getByText("new name")
|
||||
.first()
|
||||
.click();
|
||||
// check cancel rename
|
||||
await page.getByRole("combobox").click();
|
||||
await page.getByLabel("Rename").getByText("Rename").click();
|
||||
await page.getByRole("textbox").fill("cancel name");
|
||||
await page.getByTestId("session-selector").getByTestId("icon-X").click();
|
||||
await page.getByTestId("session-selector").getByText("new name").click();
|
||||
await page.mouse.move(0, 0);
|
||||
await page.getByTestId("session-selector").first().hover();
|
||||
await page
|
||||
.getByTestId("session-selector")
|
||||
.first()
|
||||
.getByTestId("icon-MoreHorizontal")
|
||||
.click();
|
||||
await page.getByLabel("Rename").click();
|
||||
await page
|
||||
.getByTestId("session-selector")
|
||||
.first()
|
||||
.getByRole("textbox")
|
||||
.fill("cancel name");
|
||||
await page
|
||||
.getByTestId("session-selector")
|
||||
.first()
|
||||
.getByTestId("icon-X")
|
||||
.click();
|
||||
await page
|
||||
.getByTestId("session-selector")
|
||||
.getByText("new name")
|
||||
.first()
|
||||
.click();
|
||||
// check cancel rename blur
|
||||
await page.getByRole("combobox").click();
|
||||
await page.getByLabel("Rename").getByText("Rename").click();
|
||||
await page.getByRole("textbox").fill("cancel_blur");
|
||||
await page.mouse.move(0, 0);
|
||||
await page.getByTestId("session-selector").first().hover();
|
||||
await page
|
||||
.getByTestId("session-selector")
|
||||
.first()
|
||||
.getByTestId("icon-MoreHorizontal")
|
||||
.click();
|
||||
await page.getByLabel("Rename").click();
|
||||
await page
|
||||
.getByTestId("session-selector")
|
||||
.first()
|
||||
.getByRole("textbox")
|
||||
.fill("cancel_blur");
|
||||
await page.getByText("PlaygroundChat").click();
|
||||
await page.getByTestId("session-selector").getByText("new name").click();
|
||||
await page
|
||||
.getByTestId("session-selector")
|
||||
.getByText("new name")
|
||||
.first()
|
||||
.click();
|
||||
// check delete session
|
||||
await page.getByRole("combobox").click();
|
||||
await page.mouse.move(0, 0);
|
||||
await page.getByTestId("session-selector").first().hover();
|
||||
await page
|
||||
.getByTestId("session-selector")
|
||||
.first()
|
||||
.getByTestId("icon-MoreHorizontal")
|
||||
.click();
|
||||
await page.getByLabel("Delete").click();
|
||||
await page.getByRole("heading", { name: "New chat" }).click();
|
||||
await page.getByTestId("new-chat").click();
|
||||
// check new session
|
||||
await page.getByTestId("input-chat-playground").click();
|
||||
await page
|
||||
@ -166,7 +218,7 @@ test(
|
||||
.fill("session_after_delete");
|
||||
await page.keyboard.press("Enter");
|
||||
await page.getByTestId("chat-message-User-session_after_delete").click();
|
||||
await expect(page.getByTestId("session-selector")).toBeVisible();
|
||||
await expect(page.getByTestId("session-selector").first()).toBeVisible();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
|
||||
@ -65,19 +65,28 @@ test(
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await tryDeleteEdge(page);
|
||||
await page.locator(".react-flow__edge-path").nth(0).click();
|
||||
await page.keyboard.press("Delete");
|
||||
let numberOfEdges = await page.locator(".react-flow__edge-path").count();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Delete edges one by one (when unlocked, should work)
|
||||
await page.locator(".react-flow__edge").nth(0).click();
|
||||
await page.waitForTimeout(200);
|
||||
await page.keyboard.press("Backspace");
|
||||
await page.waitForTimeout(300);
|
||||
let numberOfEdges = await page.locator(".react-flow__edge").count();
|
||||
expect(numberOfEdges).toBe(2);
|
||||
|
||||
await page.locator(".react-flow__edge-path").nth(0).click();
|
||||
await page.keyboard.press("Delete");
|
||||
numberOfEdges = await page.locator(".react-flow__edge-path").count();
|
||||
await page.locator(".react-flow__edge").nth(0).click();
|
||||
await page.waitForTimeout(200);
|
||||
await page.keyboard.press("Backspace");
|
||||
await page.waitForTimeout(300);
|
||||
numberOfEdges = await page.locator(".react-flow__edge").count();
|
||||
expect(numberOfEdges).toBe(1);
|
||||
|
||||
await page.locator(".react-flow__edge-path").nth(0).click();
|
||||
await page.keyboard.press("Delete");
|
||||
numberOfEdges = await page.locator(".react-flow__edge-path").count();
|
||||
await page.locator(".react-flow__edge").nth(0).click();
|
||||
await page.waitForTimeout(200);
|
||||
await page.keyboard.press("Backspace");
|
||||
await page.waitForTimeout(300);
|
||||
numberOfEdges = await page.locator(".react-flow__edge").count();
|
||||
expect(numberOfEdges).toBe(0);
|
||||
|
||||
await tryConnectNodes(page);
|
||||
@ -100,7 +109,8 @@ test(
|
||||
)
|
||||
.click();
|
||||
await page.getByTestId("handle-chatoutput-shownode-inputs-left").click();
|
||||
numberOfEdges = await page.locator(".react-flow__edge-path").count();
|
||||
await page.waitForTimeout(300);
|
||||
numberOfEdges = await page.locator(".react-flow__edge").count();
|
||||
|
||||
expect(numberOfEdges).toBe(3);
|
||||
},
|
||||
@ -110,7 +120,7 @@ async function tryConnectNodes(page: Page) {
|
||||
await lockFlow(page);
|
||||
|
||||
const numberOfTries = 5;
|
||||
let numberOfEdges = await page.locator(".react-flow__edge-path").count();
|
||||
let numberOfEdges = await page.locator(".react-flow__edge").count();
|
||||
|
||||
for (let i = 0; i < numberOfTries; i++) {
|
||||
try {
|
||||
@ -118,7 +128,7 @@ async function tryConnectNodes(page: Page) {
|
||||
timeout: 500,
|
||||
});
|
||||
} catch (_e) {
|
||||
numberOfEdges = await page.locator(".react-flow__edge-path").count();
|
||||
numberOfEdges = await page.locator(".react-flow__edge").count();
|
||||
expect(numberOfEdges).toBe(0);
|
||||
}
|
||||
|
||||
@ -131,7 +141,7 @@ async function tryConnectNodes(page: Page) {
|
||||
timeout: 500,
|
||||
});
|
||||
} catch (_e) {
|
||||
numberOfEdges = await page.locator(".react-flow__edge-path").count();
|
||||
numberOfEdges = await page.locator(".react-flow__edge").count();
|
||||
expect(numberOfEdges).toBe(0);
|
||||
}
|
||||
}
|
||||
@ -141,20 +151,18 @@ async function tryConnectNodes(page: Page) {
|
||||
async function tryDeleteEdge(page: Page) {
|
||||
await lockFlow(page);
|
||||
|
||||
const numberOfEdges = await page.locator(".react-flow__edge-path").count();
|
||||
let numberOfEdges = await page.locator(".react-flow__edge").count();
|
||||
expect(numberOfEdges).toBe(3);
|
||||
const numberOfTries = 5;
|
||||
|
||||
// When locked, clicking edges and pressing delete should not remove them
|
||||
for (let i = 0; i < numberOfTries; i++) {
|
||||
await expect(
|
||||
page.locator(".react-flow__edge-path").nth(0).click({ timeout: 500 }),
|
||||
).rejects.toThrow();
|
||||
await expect(
|
||||
page.locator(".react-flow__edge-path").nth(1).click({ timeout: 500 }),
|
||||
).rejects.toThrow();
|
||||
await expect(
|
||||
page.locator(".react-flow__edge-path").nth(2).click({ timeout: 500 }),
|
||||
).rejects.toThrow();
|
||||
await page.locator(".react-flow__edge").nth(0).click();
|
||||
await page.waitForTimeout(200);
|
||||
await page.keyboard.press("Backspace");
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
numberOfEdges = await page.locator(".react-flow__edge").count();
|
||||
expect(numberOfEdges).toBe(3);
|
||||
}
|
||||
await unlockFlow(page);
|
||||
|
||||
@ -75,7 +75,7 @@ test(
|
||||
|
||||
await page.getByTestId("save-name-description-button").click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.getByTestId("handle-conditionalrouter-shownode-true-right")
|
||||
@ -97,7 +97,7 @@ test(
|
||||
await page.getByTestId("popover-anchor-input-input_text").fill("1");
|
||||
await page.getByTestId("popover-anchor-input-match_text").fill("1");
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByTestId("button_run_text output").click();
|
||||
|
||||
@ -118,12 +118,12 @@ test(
|
||||
|
||||
// Now we will change the input to make the flow go through the other branch of the If-Else component
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByTestId("popover-anchor-input-input_text").fill("2");
|
||||
await page.getByTestId("button_run_textoutputfalse").click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.waitForSelector("text=built successfully", { timeout: 30000 });
|
||||
|
||||
@ -142,14 +142,14 @@ test(
|
||||
|
||||
// retest to make sure we can run again the flow with the first branch of the If-Else component
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByTestId("popover-anchor-input-input_text").fill("1");
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByTestId("button_run_text output").click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.waitForSelector("text=built successfully", { timeout: 30000 });
|
||||
|
||||
|
||||
@ -57,12 +57,22 @@ test(
|
||||
|
||||
await page.getByTestId("playground-btn-flow-io").click();
|
||||
|
||||
// Wait for chat messages to be fully loaded/streamed
|
||||
await page.waitForSelector('[data-testid="div-chat-message"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
// Wait for streaming to complete
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const textContents = await page
|
||||
.getByTestId("div-chat-message")
|
||||
.allTextContents();
|
||||
|
||||
const concatAllText = textContents.join(" ");
|
||||
|
||||
// Ensure we captured a non-empty response
|
||||
expect(concatAllText.length).toBeGreaterThan(0);
|
||||
|
||||
await page.getByText("Close").last().click();
|
||||
|
||||
await page.getByText("Prompt", { exact: true }).last().click();
|
||||
@ -98,12 +108,18 @@ test(
|
||||
|
||||
await page.getByTestId("playground-btn-flow-io").click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
// Wait for chat messages to be fully loaded/streamed
|
||||
await page.waitForSelector('[data-testid="div-chat-message"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
// Wait for streaming to complete
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const textContents2 = await page
|
||||
.getByTestId("div-chat-message")
|
||||
.allTextContents();
|
||||
|
||||
// The frozen node should return the same cached output
|
||||
textContents2.forEach((text) => {
|
||||
expect(text).toBe(concatAllText);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user