feat: Canvas toolbar update and enhancement (#9239)
* feat: Add EditorConfig and Prettier configuration files for consistent code formatting - Introduced .editorconfig to enforce coding styles across various file types. - Added .prettierrc for Prettier configuration to standardize code formatting. - Updated settings.json to define tab sizes and formatting options for different languages. - Enhanced canvasControlsComponent with a new dropdown for control buttons, improving UI interaction. - Refactored logCanvasControlsComponent and MemoizedComponents for better structure and consistency. * [autofix.ci] apply automated fixes * feat: Enhance canvas controls with keyboard shortcuts and dropdown functionality - Introduced keyboard shortcuts for zooming in, zooming out, fitting view, and resetting zoom. - Refactored DropdownControlButton to improve accessibility and usability. - Updated CanvasControls to handle keyboard events for enhanced user interaction. - Added helper function to format zoom percentage for better display. - Improved structure and readability of the canvas controls component. * [autofix.ci] apply automated fixes * feat: Introduce CanvasControlsDropdown and HelpDropdown components - Added new dropdown components for canvas controls and help functionalities. - Refactored CanvasControls to utilize the new dropdowns, enhancing UI interaction. - Improved keyboard shortcut handling for zoom and view adjustments. - Streamlined component structure for better maintainability and readability. * refactor: Update dropdown components and styles in canvasControls - Removed unused ShadTooltip import from dropdowns.tsx. - Adjusted padding and margin styles for DropdownControlButton and CanvasControlsDropdown for improved layout. - Enhanced button styles in MemoizedComponents for consistency. - Cleaned up class names in CanvasControls for better readability. * feat: Enhance HelpDropdown with navigation and external links - Added functionality to open documentation and desktop download links in new tabs. - Integrated navigation for shortcuts settings within the HelpDropdown. - Cleaned up commented-out code for better readability and maintainability. * [autofix.ci] apply automated fixes * feat: Add tests for CanvasControls and Dropdown components - Introduced unit tests for CanvasControls, verifying rendering and keyboard shortcut handling. - Added tests for CanvasControlsDropdown and HelpDropdown, ensuring proper functionality and interaction. - Implemented mocks for external dependencies to isolate component behavior during testing. - Enhanced test coverage for dropdown interactions and navigation features. * refactor: Simplify Jest setup by removing unused polyfills and mocks - Removed unnecessary polyfills for ResizeObserver and IntersectionObserver from the Jest setup file. - Cleaned up the window.open mock to streamline the testing environment. * feat: Enhance FlowMenu and EditFlowSettings components - Reintroduced memoization and hotkey handling in FlowMenu for improved performance. - Updated EditFlowSettings to include locking functionality with a switch component, enhancing user control over flow settings. - Adjusted layout and styling for better user experience in the EditFlowSettings component. * [autofix.ci] apply automated fixes * feat: Implement locking functionality in FlowSettingsComponent - Added state management for the locked property in FlowSettingsComponent. - Updated EditFlowSettings to disable input fields based on the locked state. - Enhanced the flow submission logic to include the locked status when saving changes. * [autofix.ci] apply automated fixes * chore: Remove EditorConfig and Prettier configuration files - Deleted .editorconfig and .prettierrc files to streamline project configuration. - Updated .cursor/settings.json to enable biome without specific editor settings for various file types. * chore: Update biome settings in .cursor/settings.json - Removed the biome.enabled property to simplify configuration. - Retained the biome.configurationPath for continued functionality. * feat: Enhance dropdown functionality with toggle options and bug report link - Added a toggle feature to the DropdownControlButton for enabling smart guides. - Introduced a new BUG_REPORT_URL constant for reporting issues. - Updated HelpDropdown to utilize the new toggle functionality and link for bug reporting. - Cleaned up imports and adjusted component props for better usability. * fix: Correct onClick handling in DropdownControlButton component - Updated the onClick prop to directly use the provided onClick function instead of a conditional. - Adjusted the handleOnNewValue prop in ToggleShadComponent to ensure proper toggle functionality. * [autofix.ci] apply automated fixes * feat: Introduce CanvasControls and dropdown components for enhanced canvas interaction - Added CanvasControls component to manage canvas interactions and state based on flow lock status. - Implemented CanvasControlsDropdown and HelpDropdown for zoom and help functionalities, respectively. - Created DropdownControlButton for reusable dropdown actions with keyboard shortcuts. - Removed deprecated dropdowns and refactored related tests to align with new component structure. - Introduced utility functions for zoom percentage formatting and modifier key detection. * refactor: Clean up imports and enhance HelpDropdown functionality - Removed redundant imports in FlowMenu and HelpDropdown components. - Refactored HelpDropdown to utilize Zustand for managing helper line state. - Improved readability by adjusting component structure and formatting. * Revert "Merge branch 'lfoss-1889' of https://github.com/langflow-ai/langflow into lfoss-1889" This reverts commit 896436ac53986702dd162151e7fdb770689751c7, reversing changes made to 475c2fa027211021557ff37bd056627be896b59c. * [autofix.ci] apply automated fixes * Reapply "Merge branch 'lfoss-1889' of https://github.com/langflow-ai/langflow into lfoss-1889" This reverts commit 4e971f81e4d2e01a75bfc855467d60536d668eb4. * fix proxy * test: Enhance testing for canvas controls and dropdown components - Updated CanvasControls test to verify state management and rendering. - Added comprehensive tests for CanvasControlsDropdown, including zoom functionality and keyboard shortcuts. - Introduced tests for utility functions in canvasUtils, ensuring correct zoom formatting and modifier key detection. - Created tests for DropdownControlButton, validating rendering, click handling, and toggle functionality. - Refactored Dropdowns test to remove CanvasControlsDropdown references and streamline imports. * [autofix.ci] apply automated fixes * refactor: reorganize imports and optimize state selection in FlowMenu component - Moved import statements for memo, useMemo, useRef, and useState to the correct position. - Updated currentFlow state selection to use useShallow for improved performance. * [autofix.ci] apply automated fixes * refactor: reorganize imports and enhance DropdownControlButton functionality - Moved import statements for React and other components to appropriate locations. - Added externalLink prop to DropdownControlButton for improved link handling. - Updated HelpDropdown to utilize externalLink prop for relevant buttons. - Refined HeaderMenuItemLink styles for better visual consistency. * [autofix.ci] apply automated fixes * test: enhance accessibility features in DropdownControlButton tests - Added role, tabIndex, aria-checked, and aria-label attributes for improved accessibility. - Implemented keyboard interaction handling for Enter and Space keys to trigger value changes. * [autofix.ci] apply automated fixes * refactor: reorganize imports and enhance FlowMenu state management - Moved import statements for React hooks to the correct position. - Updated state selection to include isFlowLocked for improved clarity and performance. - Refined usage of currentFlow state properties in the MenuBar component. * [autofix.ci] apply automated fixes * test: add Flow Lock feature tests for locking and unlocking flows - Implemented tests to verify the locking and unlocking functionality of flows, including UI changes and state persistence. - Ensured that inputs are enabled/disabled correctly based on the lock state and that the appropriate lock/unlock icons are displayed in the settings. * [autofix.ci] apply automated fixes * test: Update selectors in various specs to use canvas_controls_dropdown Replaced instances of 'fit_view' selector with 'canvas_controls_dropdown' in multiple test files to ensure proper interaction with the canvas controls. This change enhances the reliability of the tests by targeting the correct UI elements. * [autofix.ci] apply automated fixes * test: Refactor auto-login-off.spec.ts and rename-flow.ts for improved readability and functionality Updated the auto-login-off.spec.ts file to enhance code clarity by adjusting formatting and ensuring proper selector usage. Additionally, modified the rename-flow.ts utility to include hover actions for better user interaction simulation during tests. These changes aim to improve test reliability and maintainability. * test: Enhance interaction with canvas controls in multiple specs Added interactions with the 'canvas_controls_dropdown' in various test files to improve test reliability and ensure proper UI element targeting. This update includes adjustments in auto-login-off.spec.ts, customComponentAdd.spec.ts, tableInputComponent.spec.ts, stop-button-playground.spec.ts, and generalBugs-shard-7.spec.ts, enhancing the overall functionality of the tests. * fixed tests * test: Enhance interaction with canvas controls across multiple specs Added interactions with the 'canvas_controls_dropdown' in various test files to improve test reliability and ensure proper UI element targeting. This update includes adjustments in tweaksTest.spec.ts, user-flow-state-cleanup.spec.ts, and several integration tests, enhancing the overall functionality of the tests. * [autofix.ci] apply automated fixes * test: Further enhance interactions with canvas controls in multiple specs Added additional clicks on the 'canvas_controls_dropdown' in stop-building.spec.ts, promptModalComponent.spec.ts, minimize.spec.ts, and generalBugs-shard-11.spec.ts to improve test reliability and ensure proper UI element targeting. This update continues to enhance the overall functionality of the tests. * [autofix.ci] apply automated fixes * test: Improve interactions with canvas controls in various specs Added multiple clicks on the 'canvas_controls_dropdown' across several test files, including playground.spec.ts, decisionFlow.spec.ts, and generalBugs-shard-5.spec.ts, to enhance test reliability and ensure accurate UI element targeting. This update continues to refine the overall functionality of the tests. * [autofix.ci] apply automated fixes * test: Enhance canvas control interactions across multiple specs Added additional clicks on the 'canvas_controls_dropdown' in various test files, including generalBugs-shard-5.spec.ts, linkComponent.spec.ts, tabComponent.spec.ts, flowPage.spec.ts, stop-button-playground.spec.ts, and general-bugs-truncate-results.spec.ts. This update aims to improve test reliability and ensure accurate interactions with the UI elements. * [autofix.ci] apply automated fixes * test: Refine canvas control interactions in various specs Added additional clicks on the 'canvas_controls_dropdown' across multiple test files, including similarity.spec.ts, fileUploadComponent.spec.ts, sliderComponent.spec.ts, tableInputComponent.spec.ts, and minimize.spec.ts. This update aims to enhance test reliability and ensure accurate interactions with UI elements, continuing the effort to improve overall test functionality. * [autofix.ci] apply automated fixes * test: Refine canvas control interactions in loop-component.spec.ts Added additional clicks on the 'canvas_controls_dropdown' to enhance test reliability and ensure accurate interactions with UI elements. This update continues the effort to improve overall test functionality. * [autofix.ci] apply automated fixes * test: Update lock-flow interactions for improved reliability Refactored interactions in lock-flow.spec.ts to replace 'lock_unlock' clicks with more specific actions on 'flow_name' and 'lock-flow-switch'. Added necessary clicks on 'canvas_controls_dropdown' and ensured UI updates are properly awaited. This enhances the reliability of the test by ensuring accurate interactions with UI elements. * [autofix.ci] apply automated fixes * test: Enhance flow-lock.spec.ts for improved test reliability Added a wait timeout to ensure UI elements are fully rendered before interactions. Updated the test structure for better clarity and consistency in verifying the flow lock feature, ensuring accurate expectations for lock icon visibility. * [autofix.ci] apply automated fixes * test: Add unit tests for various components and improve Jest setup - Introduced unit tests for FlowMenu, HeaderMenu, CanvasControlsDropdown, EditFlowSettings, LogCanvasControls, and MemoizedComponents to enhance test coverage. - Updated jest.setup.js with global module stubs to prevent ESM/context issues in unit tests, ensuring smoother test execution. - Improved test reliability by mocking necessary components and avoiding direct imports that could lead to context issues during testing. * [autofix.ci] apply automated fixes * test: Refactor flow-lock and starter-projects tests for improved reliability - Updated flow-lock.spec.ts to replace click interactions with keyboard actions for toggling the lock switch, enhancing test stability. - Improved assertions for input states using toBeEnabled and toBeDisabled for clearer intent. - Increased timeout for waiting on UI elements in starter-projects.spec.ts to ensure proper rendering before interactions, reducing flakiness in tests. * [autofix.ci] apply automated fixes * test: Refactor file upload component tests for consistency and readability - Simplified arrow function syntax in evaluateHandle calls for better clarity. - Standardized formatting of test expectations to enhance readability. - Ensured consistent use of timeout options across various assertions to improve test reliability. * test: Update CanvasControlsDropdown test to improve accessibility - Replaced div with button in the DropdownMenu mock to enhance accessibility. - Added aria attributes to the button for better screen reader support and user experience. * [autofix.ci] apply automated fixes * feat: Refactor FlowSettingsComponent for improved functionality and testing - Introduced helper functions to manage flow updates and validation logic. - Enhanced state management for save button enable/disable conditions. - Added comprehensive unit tests for FlowSettingsComponent to ensure expected behavior and edge cases. - Improved code readability and maintainability through restructuring and modularization. * [autofix.ci] apply automated fixes * feat: Refactor HelpDropdown component for improved structure and maintainability - Extracted HelpDropdownView as a separate component to enhance readability and separation of concerns. - Updated HelpDropdown to utilize the new HelpDropdownView, simplifying its logic. - Added unit tests for HelpDropdownView to ensure functionality and interaction correctness. - Improved code organization and modularity for better maintainability. * [autofix.ci] apply automated fixes * refactor: streamline code style and improve readability in starter-projects.spec.ts - Simplified arrow function syntax by removing parentheses where not needed. - Enhanced conditional logic for handling visibility of elements in the test flow. - Improved overall code organization for better maintainability. * [autofix.ci] apply automated fixes * test: enhance flow lock feature tests for reliability and clarity - Updated test selectors for consistency and improved readability. - Replaced keyboard interaction with direct click for enabling the lock switch to reduce flakiness. - Added conditional check for the save button to ensure it is enabled before clicking. - Improved wait times for UI elements to enhance test stability. - Cleaned up code structure for better maintainability. * [autofix.ci] apply automated fixes * test: improve flow lock feature test reliability - Added a conditional check to ensure the lock switch is in the correct state after clicking. - Enhanced test stability by incorporating an additional click if the state is not as expected. * test: refine starter-projects.spec.ts for improved clarity and efficiency - Removed unnecessary wait for the fit_view selector to streamline the test flow. - Simplified the logic for clicking the fit_view button, enhancing readability and reducing redundancy. * [autofix.ci] apply automated fixes * test: further refine starter-projects.spec.ts for consistency and readability - Simplified arrow function syntax by removing unnecessary parentheses. - Enhanced code clarity by standardizing the structure of test assertions and interactions. - Improved overall test flow by ensuring consistent formatting and reducing redundancy. * [autofix.ci] apply automated fixes * revert * revert * test: enhance readability and consistency in starter-projects.spec.ts and Text Sentiment Analysis.spec.ts - Simplified arrow function syntax by removing unnecessary parentheses. - Standardized test assertions and interactions for improved clarity. - Adjusted text analysis assertion to ensure it checks for a minimum length of 50 characters. - Added a wait for selector in the starter-projects.spec.ts to ensure proper element visibility before interaction. * [autofix.ci] apply automated fixes * refactored starter projects test --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com> Co-authored-by: Lucas Oliveira <lucas.edu.oli@hotmail.com>
This commit is contained in:
parent
bd1bec6224
commit
15edb5ad14
115 changed files with 2758 additions and 278 deletions
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"biome.configurationPath": "src/frontend/biome.json"
|
||||
"biome.configurationPath": "src/frontend/biome.json"
|
||||
}
|
||||
|
|
@ -48,3 +48,59 @@ if (!Array.prototype.toSorted) {
|
|||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Global module stubs to avoid ESM/context issues in unit tests
|
||||
jest.mock("@radix-ui/react-form", () => ({
|
||||
__esModule: true,
|
||||
Field: (props) => props.children,
|
||||
Label: (props) => props.children,
|
||||
Control: (props) => props.children,
|
||||
Message: (props) => props.children,
|
||||
Submit: (props) => props.children,
|
||||
Root: (props) => props.children,
|
||||
}));
|
||||
|
||||
jest.mock("react-markdown", () => ({ __esModule: true, default: () => null }));
|
||||
|
||||
jest.mock("lucide-react/dynamicIconImports", () => ({}), { virtual: true });
|
||||
|
||||
// Avoid darkStore import in tests via genericIconComponent
|
||||
jest.mock("@/components/common/genericIconComponent", () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}));
|
||||
|
||||
// Stub custom icon that uses JSX file to avoid transform issues in Jest
|
||||
jest.mock("@/icons/BotMessageSquare", () => ({
|
||||
__esModule: true,
|
||||
BotMessageSquareIcon: () => null,
|
||||
}));
|
||||
|
||||
// Provide a minimal import.meta.env shim used in some stores when running under Jest
|
||||
if (typeof global.import === "undefined") {
|
||||
global.import = { meta: { env: { CI: process.env.CI || false } } };
|
||||
} else if (!global.import.meta) {
|
||||
global.import.meta = { env: { CI: process.env.CI || false } };
|
||||
} else if (!global.import.meta.env) {
|
||||
global.import.meta.env = { CI: process.env.CI || false };
|
||||
}
|
||||
|
||||
// Mock darkStore to avoid import.meta usage in modules during tests
|
||||
jest.mock("@/stores/darkStore", () => ({
|
||||
__esModule: true,
|
||||
useDarkStore: (selector) =>
|
||||
selector
|
||||
? selector({
|
||||
dark: false,
|
||||
stars: 0,
|
||||
version: "",
|
||||
latestVersion: "",
|
||||
discordCount: 0,
|
||||
refreshLatestVersion: () => {},
|
||||
setDark: () => {},
|
||||
refreshVersion: () => {},
|
||||
refreshStars: () => {},
|
||||
refreshDiscordCount: () => {},
|
||||
})
|
||||
: {},
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import MenuBar from "../index";
|
||||
|
||||
jest.mock("@/components/ui/button", () => ({
|
||||
Button: ({ children, ...rest }) => <button {...rest}>{children}</button>,
|
||||
}));
|
||||
jest.mock("@/components/common/genericIconComponent", () => ({
|
||||
__esModule: true,
|
||||
default: ({ name }) => <span data-testid="icon">{name}</span>,
|
||||
}));
|
||||
jest.mock("@/components/common/shadTooltipComponent", () => ({
|
||||
__esModule: true,
|
||||
default: ({ children }) => <div>{children}</div>,
|
||||
}));
|
||||
jest.mock("@/components/core/flowSettingsComponent", () => ({
|
||||
__esModule: true,
|
||||
default: () => <div data-testid="flow-settings" />,
|
||||
}));
|
||||
jest.mock(
|
||||
"@/controllers/API/queries/flows/use-get-refresh-flows-query",
|
||||
() => ({ __esModule: true, useGetRefreshFlowsQuery: () => ({}) }),
|
||||
);
|
||||
jest.mock("@/controllers/API/queries/folders/use-get-folders", () => ({
|
||||
__esModule: true,
|
||||
useGetFoldersQuery: () => ({
|
||||
data: [{ id: "f1", name: "Folder" }],
|
||||
isFetched: true,
|
||||
}),
|
||||
}));
|
||||
const mockSave = jest.fn(() => Promise.resolve());
|
||||
jest.mock("@/hooks/flows/use-save-flow", () => ({
|
||||
__esModule: true,
|
||||
default: () => mockSave,
|
||||
}));
|
||||
jest.mock("@/hooks/use-unsaved-changes", () => ({
|
||||
__esModule: true,
|
||||
useUnsavedChanges: () => true,
|
||||
}));
|
||||
jest.mock("@/customization/hooks/use-custom-navigate", () => ({
|
||||
__esModule: true,
|
||||
useCustomNavigate: () => jest.fn(),
|
||||
}));
|
||||
jest.mock("@/stores/flowsManagerStore", () => ({
|
||||
__esModule: true,
|
||||
default: (sel) =>
|
||||
sel({
|
||||
autoSaving: false,
|
||||
saveLoading: false,
|
||||
currentFlow: { updated_at: new Date().toISOString() },
|
||||
}),
|
||||
}));
|
||||
jest.mock("@/stores/alertStore", () => ({
|
||||
__esModule: true,
|
||||
default: (sel) => sel({ setSuccessData: jest.fn() }),
|
||||
}));
|
||||
jest.mock("@/stores/flowStore", () => ({
|
||||
__esModule: true,
|
||||
default: (sel) =>
|
||||
sel({
|
||||
onFlowPage: true,
|
||||
isBuilding: false,
|
||||
currentFlow: {
|
||||
id: "1",
|
||||
name: "Flow",
|
||||
folder_id: "f1",
|
||||
icon: "Workflow",
|
||||
gradient: "0",
|
||||
locked: false,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
jest.mock("@/stores/shortcuts", () => ({
|
||||
__esModule: true,
|
||||
useShortcutsStore: (sel) => sel({ changesSave: "mod+s" }),
|
||||
}));
|
||||
|
||||
// Avoid pulling utils that depend on darkStore
|
||||
jest.mock("@/utils/utils", () => ({
|
||||
__esModule: true,
|
||||
cn: (...args) => args.filter(Boolean).join(" "),
|
||||
getNumberFromString: () => 0,
|
||||
}));
|
||||
|
||||
// styleUtils imports lucide dynamic icons; stub to avoid resolution
|
||||
jest.mock("lucide-react/dynamicIconImports", () => ({}), { virtual: true });
|
||||
|
||||
describe("FlowMenu MenuBar", () => {
|
||||
it("renders current folder and flow name, enables save", async () => {
|
||||
render(<MenuBar />);
|
||||
expect(screen.getByTestId("menu_bar_wrapper")).toBeInTheDocument();
|
||||
expect(screen.getByText("Folder")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("flow_name").textContent).toBe("Flow");
|
||||
|
||||
const saveBtn = screen.getByTestId("save-flow-button");
|
||||
expect(saveBtn).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it("clicking save calls save flow", () => {
|
||||
mockSave.mockClear();
|
||||
render(<MenuBar />);
|
||||
fireEvent.click(screen.getByTestId("save-flow-button"));
|
||||
expect(mockSave).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -33,6 +33,7 @@ export const MenuBar = memo((): JSX.Element => {
|
|||
const saveFlow = useSaveFlow();
|
||||
const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
|
||||
const {
|
||||
isFlowLocked,
|
||||
currentFlowName,
|
||||
currentFlowId,
|
||||
currentFlowFolderId,
|
||||
|
|
@ -40,6 +41,7 @@ export const MenuBar = memo((): JSX.Element => {
|
|||
currentFlowGradient,
|
||||
} = useFlowStore(
|
||||
useShallow((state) => ({
|
||||
isFlowLocked: state.currentFlow?.locked,
|
||||
currentFlowName: state.currentFlow?.name,
|
||||
currentFlowId: state.currentFlow?.id,
|
||||
currentFlowFolderId: state.currentFlow?.folder_id,
|
||||
|
|
@ -140,6 +142,9 @@ export const MenuBar = memo((): JSX.Element => {
|
|||
>
|
||||
{currentFlowName || "Untitled Flow"}
|
||||
</span>
|
||||
{isFlowLocked && (
|
||||
<IconComponent name="Lock" className="h-5 w-3.5" />
|
||||
)}
|
||||
|
||||
<IconComponent
|
||||
name="pencil"
|
||||
|
|
@ -195,7 +200,6 @@ export const MenuBar = memo((): JSX.Element => {
|
|||
align="center"
|
||||
sideOffset={15}
|
||||
>
|
||||
<span className="text-sm font-semibold">Flow Details</span>
|
||||
<FlowSettingsComponent
|
||||
close={() => setOpenSettings(false)}
|
||||
open={openSettings}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import {
|
||||
HeaderMenu,
|
||||
HeaderMenuItemButton,
|
||||
HeaderMenuItemLink,
|
||||
HeaderMenuItems,
|
||||
HeaderMenuItemsSection,
|
||||
HeaderMenuItemsTitle,
|
||||
HeaderMenuToggle,
|
||||
} from "../index";
|
||||
|
||||
jest.mock("@/components/ui/dropdown-menu", () => ({
|
||||
DropdownMenu: ({ children }) => <div data-testid="dm">{children}</div>,
|
||||
DropdownMenuTrigger: ({ children, ...rest }) => (
|
||||
<button data-testid="trigger" {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
DropdownMenuContent: ({ children, ...rest }) => (
|
||||
<div data-testid="content" {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
DropdownMenuItem: ({ children, ...rest }) => (
|
||||
<div role="menuitem" {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
DropdownMenuSeparator: (props) => <hr data-testid="sep" {...props} />,
|
||||
}));
|
||||
jest.mock("@/components/common/genericIconComponent", () => ({
|
||||
__esModule: true,
|
||||
default: ({ name }) => <span data-testid="icon">{name}</span>,
|
||||
}));
|
||||
|
||||
// Avoid pulling darkStore via utils
|
||||
jest.mock("@/utils/utils", () => ({
|
||||
__esModule: true,
|
||||
cn: (...args) => args.filter(Boolean).join(" "),
|
||||
}));
|
||||
|
||||
describe("HeaderMenu primitives", () => {
|
||||
it("renders toggle and items with title and section", () => {
|
||||
render(
|
||||
<HeaderMenu>
|
||||
<HeaderMenuToggle>avatar</HeaderMenuToggle>
|
||||
<HeaderMenuItems position="right">
|
||||
<HeaderMenuItemsTitle subTitle="sub">Title</HeaderMenuItemsTitle>
|
||||
<HeaderMenuItemsSection>
|
||||
<HeaderMenuItemLink href="#" newPage>
|
||||
Docs
|
||||
</HeaderMenuItemLink>
|
||||
<HeaderMenuItemButton icon="logout" onClick={() => {}}>
|
||||
Logout
|
||||
</HeaderMenuItemButton>
|
||||
</HeaderMenuItemsSection>
|
||||
</HeaderMenuItems>
|
||||
</HeaderMenu>,
|
||||
);
|
||||
expect(screen.getByTestId("user_menu_button")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("content")).toBeInTheDocument();
|
||||
expect(screen.getAllByRole("menuitem").length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("HeaderMenuItemLink renders anchor with icon when newPage", () => {
|
||||
render(
|
||||
<HeaderMenu>
|
||||
<HeaderMenuItems>
|
||||
<HeaderMenuItemLink href="/x" newPage icon="external-link">
|
||||
Link
|
||||
</HeaderMenuItemLink>
|
||||
</HeaderMenuItems>
|
||||
</HeaderMenu>,
|
||||
);
|
||||
const link = screen.getByText("Link").closest("a")!;
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
expect(screen.getByTestId("icon")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("HeaderMenuItemButton invokes onClick", () => {
|
||||
const onClick = jest.fn();
|
||||
render(
|
||||
<HeaderMenu>
|
||||
<HeaderMenuItems>
|
||||
<HeaderMenuItemButton onClick={onClick}>Click</HeaderMenuItemButton>
|
||||
</HeaderMenuItems>
|
||||
</HeaderMenu>,
|
||||
);
|
||||
fireEvent.click(screen.getByText("Click"));
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -45,7 +45,7 @@ export const HeaderMenuItemLink = ({
|
|||
{icon && (
|
||||
<ForwardedIconComponent
|
||||
name={icon}
|
||||
className="side-bar-button-size mr-3 h-[18px] w-[18px] opacity-0 transition-all duration-300 group-hover:translate-x-3 group-hover:opacity-100 group-focus-visible:translate-x-3 group-focus-visible:opacity-100"
|
||||
className="side-bar-button-size h-[18px] w-[18px] opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100"
|
||||
/>
|
||||
)}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
import { Panel, useStoreApi } from "@xyflow/react";
|
||||
import { type ReactNode, useEffect } from "react";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import CanvasControlsDropdown from "./CanvasControlsDropdown";
|
||||
import HelpDropdown from "./HelpDropdown";
|
||||
|
||||
const CanvasControls = ({ children }: { children?: ReactNode }) => {
|
||||
const reactFlowStoreApi = useStoreApi();
|
||||
const isFlowLocked = useFlowStore(
|
||||
useShallow((state) => state.currentFlow?.locked),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
reactFlowStoreApi.setState({
|
||||
nodesDraggable: !isFlowLocked,
|
||||
nodesConnectable: !isFlowLocked,
|
||||
elementsSelectable: !isFlowLocked,
|
||||
});
|
||||
}, [isFlowLocked, reactFlowStoreApi]);
|
||||
|
||||
return (
|
||||
<Panel
|
||||
data-testid="main_canvas_controls"
|
||||
className="react-flow__controls !left-auto !m-2 flex !flex-row rounded-md border border-border bg-background fill-foreground stroke-foreground text-primary [&>button]:border-0"
|
||||
position="bottom-right"
|
||||
>
|
||||
{children}
|
||||
{children && (
|
||||
<span>
|
||||
<Separator orientation="vertical" />
|
||||
</span>
|
||||
)}
|
||||
<CanvasControlsDropdown />
|
||||
<span>
|
||||
<Separator orientation="vertical" />
|
||||
</span>
|
||||
<HelpDropdown />
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
export default CanvasControls;
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
import { useReactFlow, useStore } from "@xyflow/react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { shallow } from "zustand/shallow";
|
||||
import IconComponent from "@/components/common/genericIconComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import DropdownControlButton from "./DropdownControlButton";
|
||||
import { formatZoomPercentage, reactFlowSelector } from "./utils/canvasUtils";
|
||||
|
||||
export const KEYBOARD_SHORTCUTS = {
|
||||
ZOOM_IN: { key: "+", code: "Equal" },
|
||||
ZOOM_OUT: { key: "-", code: "Minus" },
|
||||
FIT_VIEW: { key: "1", code: "Digit1" },
|
||||
RESET_ZOOM: { key: "0", code: "Digit0" },
|
||||
} as const;
|
||||
|
||||
const CanvasControlsDropdown = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { fitView, zoomIn, zoomOut, zoomTo } = useReactFlow();
|
||||
|
||||
const { minZoomReached, maxZoomReached, zoom } = useStore(
|
||||
reactFlowSelector,
|
||||
shallow,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
const isModifierPressed = event.metaKey || event.ctrlKey;
|
||||
|
||||
if (!isModifierPressed) return;
|
||||
|
||||
switch (event.code) {
|
||||
case KEYBOARD_SHORTCUTS.ZOOM_IN.code:
|
||||
event.preventDefault();
|
||||
if (!maxZoomReached) {
|
||||
zoomIn();
|
||||
}
|
||||
break;
|
||||
case KEYBOARD_SHORTCUTS.ZOOM_OUT.code:
|
||||
event.preventDefault();
|
||||
if (minZoomReached || zoom <= 0.6) {
|
||||
zoomTo(1);
|
||||
} else {
|
||||
zoomOut();
|
||||
}
|
||||
break;
|
||||
case KEYBOARD_SHORTCUTS.FIT_VIEW.code:
|
||||
event.preventDefault();
|
||||
fitView();
|
||||
break;
|
||||
case KEYBOARD_SHORTCUTS.RESET_ZOOM.code:
|
||||
event.preventDefault();
|
||||
zoomTo(1);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, [zoomIn, zoomOut, fitView, zoomTo, maxZoomReached, minZoomReached, zoom]);
|
||||
|
||||
const handleZoomIn = useCallback(() => {
|
||||
zoomIn();
|
||||
}, [zoomIn]);
|
||||
|
||||
const handleZoomOut = useCallback(() => {
|
||||
zoomOut();
|
||||
}, [zoomOut]);
|
||||
|
||||
const handleFitView = useCallback(() => {
|
||||
fitView();
|
||||
}, [fitView]);
|
||||
|
||||
const handleResetZoom = useCallback(() => {
|
||||
zoomTo(1);
|
||||
}, [zoomTo]);
|
||||
|
||||
return (
|
||||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
data-testid="canvas_controls_dropdown"
|
||||
className="group rounded-none px-2 py-2 hover:bg-muted"
|
||||
unstyled
|
||||
title="Canvas Controls"
|
||||
>
|
||||
<div className="flex items-center justify-center ">
|
||||
<div className="text-sm text-primary pr-1">
|
||||
{formatZoomPercentage(zoom)}
|
||||
</div>
|
||||
<IconComponent
|
||||
name={isOpen ? "ChevronDown" : "ChevronUp"}
|
||||
aria-hidden="true"
|
||||
className="text-primary group-hover:text-primary !h-5 !w-5"
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
side="top"
|
||||
align="end"
|
||||
className="flex flex-col w-full"
|
||||
>
|
||||
<DropdownControlButton
|
||||
tooltipText="Zoom In"
|
||||
onClick={handleZoomIn}
|
||||
disabled={maxZoomReached}
|
||||
testId="zoom_in"
|
||||
label="Zoom In"
|
||||
shortcut={KEYBOARD_SHORTCUTS.ZOOM_IN.key}
|
||||
/>
|
||||
<DropdownControlButton
|
||||
tooltipText="Zoom Out"
|
||||
onClick={handleZoomOut}
|
||||
disabled={minZoomReached}
|
||||
testId="zoom_out"
|
||||
label="Zoom Out"
|
||||
shortcut={KEYBOARD_SHORTCUTS.ZOOM_OUT.key}
|
||||
/>
|
||||
<Separator />
|
||||
<DropdownControlButton
|
||||
tooltipText="Reset zoom to 100%"
|
||||
onClick={handleResetZoom}
|
||||
testId="reset_zoom"
|
||||
label="Zoom To 100%"
|
||||
shortcut={KEYBOARD_SHORTCUTS.RESET_ZOOM.key}
|
||||
/>
|
||||
<DropdownControlButton
|
||||
tooltipText="Fit view to show all nodes"
|
||||
onClick={handleFitView}
|
||||
testId="fit_view"
|
||||
label="Zoom To Fit"
|
||||
shortcut={KEYBOARD_SHORTCUTS.FIT_VIEW.key}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default CanvasControlsDropdown;
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
import React from "react";
|
||||
import { ForwardedIconComponent } from "@/components/common/genericIconComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/utils/utils";
|
||||
import ToggleShadComponent from "../parameterRenderComponent/components/toggleShadComponent";
|
||||
import { getModifierKey } from "./utils/canvasUtils";
|
||||
|
||||
export type DropdownControlButtonProps = {
|
||||
tooltipText?: string;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
testId?: string;
|
||||
label?: string;
|
||||
shortcut?: string;
|
||||
iconName?: string;
|
||||
hasToogle?: boolean;
|
||||
toggleValue?: boolean;
|
||||
externalLink?: boolean;
|
||||
};
|
||||
|
||||
const DropdownControlButton: React.FC<DropdownControlButtonProps> = ({
|
||||
tooltipText,
|
||||
onClick = () => {},
|
||||
disabled,
|
||||
testId,
|
||||
label = "",
|
||||
shortcut = "",
|
||||
iconName,
|
||||
hasToogle = false,
|
||||
toggleValue = false,
|
||||
externalLink = false,
|
||||
}) => (
|
||||
<Button
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
"group flex items-center justify-center !py-1.5 !px-2 hover:bg-accent h-full rounded-none",
|
||||
disabled && "cursor-not-allowed opacity-50",
|
||||
)}
|
||||
onClick={onClick}
|
||||
variant="ghost"
|
||||
disabled={disabled}
|
||||
title={tooltipText || ""}
|
||||
>
|
||||
{iconName && (
|
||||
<ForwardedIconComponent
|
||||
name={iconName}
|
||||
className="text-muted-foreground group-hover:text-primary"
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-row items-center justify-between w-full h-full">
|
||||
<span className="text-muted-foreground text-sm mr-2 group-hover:text-primary">
|
||||
{label}
|
||||
</span>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-row items-center text-sm",
|
||||
shortcut && "w-[25px]",
|
||||
)}
|
||||
>
|
||||
{shortcut && (
|
||||
<div className="flex items-center justify-between w-full text-muted-foreground group-hover:text-primary">
|
||||
<span>{getModifierKey()}</span>
|
||||
<span className="">{shortcut}</span>
|
||||
</div>
|
||||
)}
|
||||
{externalLink && (
|
||||
<ForwardedIconComponent
|
||||
name="external-link"
|
||||
className="text-muted-foreground group-hover:text-primary opacity-0 group-hover:opacity-100"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{hasToogle && (
|
||||
<ToggleShadComponent
|
||||
value={toggleValue}
|
||||
handleOnNewValue={onClick}
|
||||
editNode={true}
|
||||
id="helper_lines"
|
||||
disabled={false}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
export default DropdownControlButton;
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import { useCallback, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { HelpDropdownView } from "@/components/core/canvasControlsComponent/HelpDropdownView";
|
||||
import {
|
||||
BUG_REPORT_URL,
|
||||
DATASTAX_DOCS_URL,
|
||||
DESKTOP_URL,
|
||||
DOCS_URL,
|
||||
} from "@/constants/constants";
|
||||
import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
|
||||
const HelpDropdown = () => {
|
||||
const navigate = useNavigate();
|
||||
const [isHelpMenuOpen, setIsHelpMenuOpen] = useState(false);
|
||||
const helperLineEnabled = useFlowStore((state) => state.helperLineEnabled);
|
||||
const setHelperLineEnabled = useFlowStore(
|
||||
(state) => state.setHelperLineEnabled,
|
||||
);
|
||||
|
||||
const onToggleHelperLines = useCallback(() => {
|
||||
setHelperLineEnabled(!helperLineEnabled);
|
||||
}, [helperLineEnabled]);
|
||||
|
||||
const docsUrl = ENABLE_DATASTAX_LANGFLOW ? DATASTAX_DOCS_URL : DOCS_URL;
|
||||
|
||||
return (
|
||||
<HelpDropdownView
|
||||
isOpen={isHelpMenuOpen}
|
||||
onOpenChange={setIsHelpMenuOpen}
|
||||
helperLineEnabled={helperLineEnabled}
|
||||
onToggleHelperLines={onToggleHelperLines}
|
||||
navigateTo={(path) => navigate(path)}
|
||||
openLink={(url) => window.open(url, "_blank")}
|
||||
urls={{ docs: docsUrl, bugReport: BUG_REPORT_URL, desktop: DESKTOP_URL }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default HelpDropdown;
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
import IconComponent from "@/components/common/genericIconComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import DropdownControlButton from "./DropdownControlButton";
|
||||
|
||||
export type HelpDropdownViewProps = {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
helperLineEnabled: boolean;
|
||||
onToggleHelperLines: () => void;
|
||||
navigateTo: (path: string) => void;
|
||||
openLink: (url: string) => void;
|
||||
urls: {
|
||||
docs: string;
|
||||
bugReport: string;
|
||||
desktop: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const HelpDropdownView = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
helperLineEnabled,
|
||||
onToggleHelperLines,
|
||||
navigateTo,
|
||||
openLink,
|
||||
urls,
|
||||
}: HelpDropdownViewProps) => {
|
||||
return (
|
||||
<DropdownMenu open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group flex items-center justify-center px-2 rounded-none"
|
||||
title="Help"
|
||||
>
|
||||
<IconComponent
|
||||
name="Circle-Help"
|
||||
aria-hidden="true"
|
||||
className="text-muted-foreground group-hover:text-primary !h-5 !w-5"
|
||||
/>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
side="top"
|
||||
align="end"
|
||||
className="flex flex-col w-full"
|
||||
>
|
||||
<DropdownControlButton
|
||||
iconName="book-open"
|
||||
testId="canvas_controls_dropdown_docs"
|
||||
label="Docs"
|
||||
externalLink
|
||||
onClick={() => openLink(urls.docs)}
|
||||
/>
|
||||
<DropdownControlButton
|
||||
iconName="keyboard"
|
||||
testId="canvas_controls_dropdown_shortcuts"
|
||||
label="Shortcuts"
|
||||
onClick={() => navigateTo("/settings/shortcuts")}
|
||||
/>
|
||||
<DropdownControlButton
|
||||
iconName="bug"
|
||||
testId="canvas_controls_dropdown_report_a_bug"
|
||||
externalLink
|
||||
label="Report a bug"
|
||||
onClick={() => openLink(urls.bugReport)}
|
||||
/>
|
||||
<Separator />
|
||||
<DropdownControlButton
|
||||
iconName="download"
|
||||
testId="canvas_controls_dropdown_get_langflow_desktop"
|
||||
label="Get Langflow Desktop"
|
||||
externalLink
|
||||
onClick={() => openLink(urls.desktop)}
|
||||
/>
|
||||
<DropdownControlButton
|
||||
iconName={!helperLineEnabled ? "UnfoldHorizontal" : "FoldHorizontal"}
|
||||
testId="canvas_controls_dropdown_enable_smart_guides"
|
||||
onClick={onToggleHelperLines}
|
||||
toggleValue={helperLineEnabled}
|
||||
label="Enable smart guides"
|
||||
hasToogle={true}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default HelpDropdownView;
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import CanvasControls from "../CanvasControls";
|
||||
|
||||
// Capture flow functions for assertions
|
||||
const reactFlowFns = {
|
||||
fitView: jest.fn(),
|
||||
zoomIn: jest.fn(),
|
||||
zoomOut: jest.fn(),
|
||||
zoomTo: jest.fn(),
|
||||
};
|
||||
|
||||
// Mocks for external dependencies used internally
|
||||
jest.mock("@xyflow/react", () => ({
|
||||
Panel: ({ children, ...props }: any) => (
|
||||
<div data-testid="panel" {...props}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
useReactFlow: () => reactFlowFns,
|
||||
useStore: (_selector: any) => ({
|
||||
isInteractive: true,
|
||||
minZoomReached: false,
|
||||
maxZoomReached: false,
|
||||
zoom: 1,
|
||||
}),
|
||||
useStoreApi: () => ({ setState: jest.fn() }),
|
||||
}));
|
||||
|
||||
jest.mock("@/stores/flowStore", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => false),
|
||||
}));
|
||||
|
||||
jest.mock("@/components/ui/separator", () => ({
|
||||
Separator: ({ orientation }: { orientation: "vertical" | "horizontal" }) => (
|
||||
<div data-testid={`separator-${orientation}`} />
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock dropdowns to a simple render that exposes props for assertions
|
||||
jest.mock("../CanvasControlsDropdown", () => ({
|
||||
__esModule: true,
|
||||
default: (props: any) => <div data-testid="controls-dropdown" {...props} />,
|
||||
}));
|
||||
|
||||
jest.mock("../HelpDropdown", () => ({
|
||||
__esModule: true,
|
||||
default: (props: any) => <div data-testid="help-dropdown" {...props} />,
|
||||
}));
|
||||
|
||||
describe("CanvasControls", () => {
|
||||
it("renders panel and separators when children present", () => {
|
||||
render(
|
||||
<CanvasControls>
|
||||
<div>child</div>
|
||||
</CanvasControls>,
|
||||
);
|
||||
expect(screen.getByTestId("main_canvas_controls")).toBeInTheDocument();
|
||||
const seps = screen.getAllByTestId("separator-vertical");
|
||||
expect(seps.length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByTestId("controls-dropdown")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("help-dropdown")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("updates reactFlow state based on flow lock status", () => {
|
||||
render(<CanvasControls />);
|
||||
|
||||
// The component should set up state through useStoreApi
|
||||
// This test verifies the component renders and doesn't throw
|
||||
expect(screen.getByTestId("main_canvas_controls")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import CanvasControlsDropdown, {
|
||||
KEYBOARD_SHORTCUTS,
|
||||
} from "../CanvasControlsDropdown";
|
||||
|
||||
jest.mock("@/components/ui/button", () => ({
|
||||
Button: ({ children, ...rest }) => <button {...rest}>{children}</button>,
|
||||
}));
|
||||
jest.mock("@/components/ui/dropdown-menu", () => ({
|
||||
DropdownMenu: ({ children, open, onOpenChange }) => (
|
||||
<div data-testid="dropdown-menu" data-open={open}>
|
||||
<button
|
||||
onClick={() => onOpenChange?.(!open)}
|
||||
aria-expanded={open}
|
||||
aria-haspopup="true"
|
||||
type="button"
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
DropdownMenuContent: ({ children }) => (
|
||||
<div data-testid="dropdown-content">{children}</div>
|
||||
),
|
||||
DropdownMenuTrigger: ({ children }) => (
|
||||
<div data-testid="dropdown-trigger">{children}</div>
|
||||
),
|
||||
}));
|
||||
jest.mock("@/components/ui/separator", () => ({
|
||||
Separator: () => <div data-testid="separator" />,
|
||||
}));
|
||||
jest.mock("../DropdownControlButton", () => ({
|
||||
__esModule: true,
|
||||
default: ({ label, onClick, disabled, testId, shortcut }) => (
|
||||
<button
|
||||
aria-label={label}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
data-testid={testId}
|
||||
>
|
||||
{label} ({shortcut})
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
jest.mock("@/components/common/genericIconComponent", () => ({
|
||||
__esModule: true,
|
||||
default: ({ name }) => <span data-testid="icon">{name}</span>,
|
||||
}));
|
||||
|
||||
// Minimize utils import surface (prevents pulling stores/darkStore via utils.ts)
|
||||
jest.mock("@/utils/utils", () => ({
|
||||
__esModule: true,
|
||||
getOS: () => "macos",
|
||||
cn: (...args) => args.filter(Boolean).join(" "),
|
||||
}));
|
||||
|
||||
const fitView = jest.fn();
|
||||
const zoomIn = jest.fn();
|
||||
const zoomOut = jest.fn();
|
||||
const zoomTo = jest.fn();
|
||||
|
||||
jest.mock("@xyflow/react", () => ({
|
||||
useReactFlow: () => ({ fitView, zoomIn, zoomOut, zoomTo }),
|
||||
useStore: (selector) =>
|
||||
selector({
|
||||
nodesDraggable: true,
|
||||
nodesConnectable: true,
|
||||
elementsSelectable: true,
|
||||
transform: [0, 0, 1],
|
||||
minZoom: 0.2,
|
||||
maxZoom: 2,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("CanvasControlsDropdown", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders current zoom percentage and toggles menu", () => {
|
||||
render(<CanvasControlsDropdown />);
|
||||
expect(screen.getByText("100%"));
|
||||
fireEvent.click(screen.getByTestId("canvas_controls_dropdown"));
|
||||
expect(screen.getByTestId("dropdown-content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles zoom in/out, fit and reset via click", () => {
|
||||
render(<CanvasControlsDropdown />);
|
||||
fireEvent.click(screen.getByTestId("canvas_controls_dropdown"));
|
||||
|
||||
fireEvent.click(screen.getByTestId("zoom_in"));
|
||||
expect(zoomIn).toHaveBeenCalled();
|
||||
|
||||
fireEvent.click(screen.getByTestId("zoom_out"));
|
||||
expect(zoomOut).toHaveBeenCalled();
|
||||
|
||||
fireEvent.click(screen.getByTestId("fit_view"));
|
||||
expect(fitView).toHaveBeenCalled();
|
||||
|
||||
fireEvent.click(screen.getByTestId("reset_zoom"));
|
||||
expect(zoomTo).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("handles keyboard shortcuts with modifier", () => {
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
const keydown = (code: string) =>
|
||||
document.dispatchEvent(
|
||||
new KeyboardEvent("keydown", { code, metaKey: true }),
|
||||
);
|
||||
|
||||
keydown(KEYBOARD_SHORTCUTS.ZOOM_IN.code);
|
||||
expect(zoomIn).toHaveBeenCalled();
|
||||
|
||||
keydown(KEYBOARD_SHORTCUTS.ZOOM_OUT.code);
|
||||
expect(zoomOut).toHaveBeenCalled();
|
||||
|
||||
keydown(KEYBOARD_SHORTCUTS.FIT_VIEW.code);
|
||||
expect(fitView).toHaveBeenCalled();
|
||||
|
||||
keydown(KEYBOARD_SHORTCUTS.RESET_ZOOM.code);
|
||||
expect(zoomTo).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import CanvasControlsDropdown, {
|
||||
KEYBOARD_SHORTCUTS,
|
||||
} from "../CanvasControlsDropdown";
|
||||
|
||||
// Mock React Flow hooks
|
||||
const mockReactFlowFns = {
|
||||
fitView: jest.fn(),
|
||||
zoomIn: jest.fn(),
|
||||
zoomOut: jest.fn(),
|
||||
zoomTo: jest.fn(),
|
||||
};
|
||||
|
||||
let mockStoreValues = {
|
||||
isInteractive: true,
|
||||
minZoomReached: false,
|
||||
maxZoomReached: false,
|
||||
zoom: 1,
|
||||
};
|
||||
|
||||
jest.mock("@xyflow/react", () => ({
|
||||
useReactFlow: () => mockReactFlowFns,
|
||||
useStore: (selector: any) => selector(mockStoreValues),
|
||||
}));
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock("zustand/shallow", () => ({
|
||||
shallow: jest.fn((fn) => fn),
|
||||
}));
|
||||
|
||||
jest.mock("@/components/common/genericIconComponent", () => ({
|
||||
__esModule: true,
|
||||
default: ({ name }: { name: string }) => (
|
||||
<span data-testid={`icon-${name}`}>{name}</span>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock("@/components/ui/button", () => ({
|
||||
Button: ({ children, ...props }: any) => (
|
||||
<button {...props}>{children}</button>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock("@/components/ui/dropdown-menu", () => ({
|
||||
DropdownMenu: ({ children, ...props }: any) => (
|
||||
<div data-testid="dropdown-menu" {...props}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
DropdownMenuTrigger: ({ children, asChild, ...props }: any) => (
|
||||
<div data-testid="dropdown-trigger" {...props}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
DropdownMenuContent: ({ children, ...props }: any) => (
|
||||
<div data-testid="dropdown-content" {...props}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock("@/components/ui/separator", () => ({
|
||||
Separator: () => <div data-testid="separator" />,
|
||||
}));
|
||||
|
||||
jest.mock("../DropdownControlButton", () => ({
|
||||
__esModule: true,
|
||||
default: ({ testId, onClick, disabled, label, shortcut }: any) => (
|
||||
<button
|
||||
data-testid={testId + "_dropdown"}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
data-label={label}
|
||||
data-shortcut={shortcut}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock("../utils/canvasUtils", () => ({
|
||||
formatZoomPercentage: jest.fn((zoom: number) => `${Math.round(zoom * 100)}%`),
|
||||
reactFlowSelector: jest.fn((state: any) => state),
|
||||
}));
|
||||
|
||||
describe("CanvasControlsDropdown", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Reset mock store values
|
||||
mockStoreValues = {
|
||||
isInteractive: true,
|
||||
minZoomReached: false,
|
||||
maxZoomReached: false,
|
||||
zoom: 1,
|
||||
};
|
||||
|
||||
// Mock addEventListener and removeEventListener
|
||||
jest.spyOn(document, "addEventListener");
|
||||
jest.spyOn(document, "removeEventListener");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("renders dropdown trigger with zoom percentage", () => {
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
expect(screen.getByTestId("canvas_controls_dropdown")).toBeInTheDocument();
|
||||
expect(screen.getByText("100%")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders chevron icon", () => {
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
// Should have one of the chevron icons (the actual logic depends on internal state)
|
||||
const chevronUp = screen.queryByTestId("icon-ChevronUp");
|
||||
const chevronDown = screen.queryByTestId("icon-ChevronDown");
|
||||
|
||||
expect(chevronUp || chevronDown).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders all control buttons with correct props", () => {
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
expect(screen.getByTestId("zoom_in_dropdown")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("zoom_out_dropdown")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("reset_zoom_dropdown")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("fit_view_dropdown")).toBeInTheDocument();
|
||||
|
||||
// Check shortcuts are passed correctly
|
||||
expect(screen.getByTestId("zoom_in_dropdown")).toHaveAttribute(
|
||||
"data-shortcut",
|
||||
KEYBOARD_SHORTCUTS.ZOOM_IN.key,
|
||||
);
|
||||
expect(screen.getByTestId("zoom_out_dropdown")).toHaveAttribute(
|
||||
"data-shortcut",
|
||||
KEYBOARD_SHORTCUTS.ZOOM_OUT.key,
|
||||
);
|
||||
});
|
||||
|
||||
it("handles zoom in button click", () => {
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("zoom_in_dropdown"));
|
||||
expect(mockReactFlowFns.zoomIn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("handles zoom out button click", () => {
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("zoom_out_dropdown"));
|
||||
expect(mockReactFlowFns.zoomOut).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("handles fit view button click", () => {
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("fit_view_dropdown"));
|
||||
expect(mockReactFlowFns.fitView).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("handles reset zoom button click", () => {
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("reset_zoom_dropdown"));
|
||||
expect(mockReactFlowFns.zoomTo).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("disables zoom in when maxZoomReached is true", () => {
|
||||
mockStoreValues.maxZoomReached = true;
|
||||
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
expect(screen.getByTestId("zoom_in_dropdown")).toBeDisabled();
|
||||
});
|
||||
|
||||
it("disables zoom out when minZoomReached is true", () => {
|
||||
mockStoreValues.minZoomReached = true;
|
||||
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
expect(screen.getByTestId("zoom_out_dropdown")).toBeDisabled();
|
||||
});
|
||||
|
||||
describe("Keyboard shortcuts", () => {
|
||||
it("sets up keyboard event listeners on mount", () => {
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
expect(document.addEventListener).toHaveBeenCalledWith(
|
||||
"keydown",
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Event listener management", () => {
|
||||
it("removes keydown event listener on unmount", () => {
|
||||
const { unmount } = render(<CanvasControlsDropdown />);
|
||||
|
||||
unmount();
|
||||
|
||||
expect(document.removeEventListener).toHaveBeenCalledWith(
|
||||
"keydown",
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Dynamic zoom display", () => {
|
||||
it("displays zoom percentage correctly", () => {
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
expect(screen.getByText("100%")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles fractional zoom values correctly", () => {
|
||||
mockStoreValues.zoom = 0.75;
|
||||
|
||||
render(<CanvasControlsDropdown />);
|
||||
|
||||
expect(screen.getByText("75%")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import DropdownControlButton from "../DropdownControlButton";
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock("@/components/common/genericIconComponent", () => ({
|
||||
ForwardedIconComponent: ({
|
||||
name,
|
||||
className,
|
||||
}: {
|
||||
name: string;
|
||||
className: string;
|
||||
}) => <span data-testid={`icon-${name}`} className={className} />,
|
||||
}));
|
||||
|
||||
jest.mock("@/components/ui/button", () => ({
|
||||
Button: ({ children, ...props }: any) => (
|
||||
<button {...props}>{children}</button>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock("@/utils/utils", () => ({
|
||||
cn: (...args: any[]) => args.filter(Boolean).join(" "),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
"../../parameterRenderComponent/components/toggleShadComponent",
|
||||
() => ({
|
||||
__esModule: true,
|
||||
default: ({ value, handleOnNewValue, id }: any) => (
|
||||
<div
|
||||
data-testid={`toggle-${id}`}
|
||||
data-value={value}
|
||||
onClick={handleOnNewValue}
|
||||
role="switch"
|
||||
tabIndex={0}
|
||||
aria-checked={value}
|
||||
aria-label={`Toggle ${id}`}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
handleOnNewValue();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock("../utils/canvasUtils", () => ({
|
||||
getModifierKey: jest.fn(() => "⌘"),
|
||||
}));
|
||||
|
||||
describe("DropdownControlButton", () => {
|
||||
const defaultProps = {
|
||||
testId: "test-button",
|
||||
label: "Test Button",
|
||||
onClick: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders basic button with label", () => {
|
||||
render(<DropdownControlButton {...defaultProps} />);
|
||||
|
||||
expect(screen.getByTestId("test-button")).toBeInTheDocument();
|
||||
expect(screen.getByText("Test Button")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls onClick handler when clicked", () => {
|
||||
const mockOnClick = jest.fn();
|
||||
render(<DropdownControlButton {...defaultProps} onClick={mockOnClick} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("test-button"));
|
||||
expect(mockOnClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("renders with icon when iconName is provided", () => {
|
||||
render(<DropdownControlButton {...defaultProps} iconName="test-icon" />);
|
||||
|
||||
expect(screen.getByTestId("icon-test-icon")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays shortcut with modifier key", () => {
|
||||
render(<DropdownControlButton {...defaultProps} shortcut="+" />);
|
||||
|
||||
expect(screen.getByText("⌘")).toBeInTheDocument();
|
||||
expect(screen.getByText("+")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies disabled state correctly", () => {
|
||||
render(<DropdownControlButton {...defaultProps} disabled />);
|
||||
|
||||
const button = screen.getByTestId("test-button");
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
it("sets tooltip text as title attribute", () => {
|
||||
const tooltipText = "This is a tooltip";
|
||||
render(
|
||||
<DropdownControlButton {...defaultProps} tooltipText={tooltipText} />,
|
||||
);
|
||||
|
||||
const button = screen.getByTestId("test-button");
|
||||
expect(button).toHaveAttribute("title", tooltipText);
|
||||
});
|
||||
|
||||
it("renders toggle component when hasToogle is true", () => {
|
||||
render(
|
||||
<DropdownControlButton
|
||||
{...defaultProps}
|
||||
hasToogle={true}
|
||||
toggleValue={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("toggle-helper_lines")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("toggle-helper_lines")).toHaveAttribute(
|
||||
"data-value",
|
||||
"true",
|
||||
);
|
||||
});
|
||||
|
||||
it("passes toggle value correctly to toggle component", () => {
|
||||
render(
|
||||
<DropdownControlButton
|
||||
{...defaultProps}
|
||||
hasToogle={true}
|
||||
toggleValue={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("toggle-helper_lines")).toHaveAttribute(
|
||||
"data-value",
|
||||
"false",
|
||||
);
|
||||
});
|
||||
|
||||
it("handles toggle click through onClick prop", () => {
|
||||
const mockOnClick = jest.fn();
|
||||
render(
|
||||
<DropdownControlButton
|
||||
{...defaultProps}
|
||||
onClick={mockOnClick}
|
||||
hasToogle={true}
|
||||
toggleValue={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId("toggle-helper_lines"));
|
||||
expect(mockOnClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders without shortcut when not provided", () => {
|
||||
render(<DropdownControlButton {...defaultProps} />);
|
||||
|
||||
expect(screen.queryByText("⌘")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("uses default onClick when not provided", () => {
|
||||
render(<DropdownControlButton testId="test-button" label="Test" />);
|
||||
|
||||
// Should not throw error when clicked
|
||||
fireEvent.click(screen.getByTestId("test-button"));
|
||||
});
|
||||
|
||||
it("applies correct CSS classes for disabled state", () => {
|
||||
render(<DropdownControlButton {...defaultProps} disabled />);
|
||||
|
||||
const button = screen.getByTestId("test-button");
|
||||
expect(button.className).toContain("cursor-not-allowed opacity-50");
|
||||
});
|
||||
|
||||
it("renders empty label by default", () => {
|
||||
render(<DropdownControlButton testId="test-button" />);
|
||||
|
||||
const button = screen.getByTestId("test-button");
|
||||
expect(button).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { MemoryRouter, useNavigate } from "react-router-dom";
|
||||
import HelpDropdown from "../HelpDropdown";
|
||||
|
||||
jest.mock("@/components/ui/button", () => ({
|
||||
Button: ({ children, ...props }: any) => (
|
||||
<button {...props}>{children}</button>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock("@/components/ui/dropdown-menu", () => ({
|
||||
DropdownMenu: ({ children, ...props }: any) => (
|
||||
<div data-testid="dropdown-menu" {...props}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
DropdownMenuTrigger: ({ children, ...props }: any) => (
|
||||
<div data-testid="dropdown-trigger" {...props}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
DropdownMenuContent: ({ children, ...props }: any) => (
|
||||
<div data-testid="dropdown-content" {...props}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock("@/components/ui/separator", () => ({
|
||||
Separator: () => <div data-testid="separator" />,
|
||||
}));
|
||||
|
||||
jest.mock("@/components/common/genericIconComponent", () => ({
|
||||
__esModule: true,
|
||||
default: () => <span data-testid="icon" />,
|
||||
ForwardedIconComponent: ({ name }: { name: string }) => (
|
||||
<span data-testid={`icon-${name}`} />
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock("@/constants/constants", () => ({
|
||||
__esModule: true,
|
||||
DATASTAX_DOCS_URL: "https://docs.datastax.com",
|
||||
DOCS_URL: "https://docs.langflow.org",
|
||||
DESKTOP_URL: "https://desktop.langflow.org",
|
||||
}));
|
||||
|
||||
jest.mock("@/customization/feature-flags", () => ({
|
||||
ENABLE_DATASTAX_LANGFLOW: false,
|
||||
}));
|
||||
|
||||
jest.mock("@/utils/utils", () => ({
|
||||
cn: (...args: any[]) => args.filter(Boolean).join(" "),
|
||||
getOS: () => "macos",
|
||||
}));
|
||||
|
||||
jest.mock("react-router-dom", () => {
|
||||
const actual = jest.requireActual("react-router-dom");
|
||||
return {
|
||||
...actual,
|
||||
useNavigate: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock("@/stores/darkStore", () => ({
|
||||
useDarkStore: () => ({
|
||||
dark: false,
|
||||
setDark: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock("@/stores/flowStore", () => ({
|
||||
__esModule: true,
|
||||
default: () => ({
|
||||
helperLineEnabled: false,
|
||||
setHelperLineEnabled: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock window.open
|
||||
Object.defineProperty(window, "open", {
|
||||
writable: true,
|
||||
value: jest.fn(),
|
||||
});
|
||||
|
||||
describe("HelpDropdown", () => {
|
||||
beforeEach(() => {
|
||||
(window.open as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
it("opens docs in new tab and navigates to shortcuts", () => {
|
||||
const mockNavigate = jest.fn();
|
||||
(useNavigate as unknown as jest.Mock).mockReturnValue(mockNavigate);
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<HelpDropdown isOpen={true} onOpenChange={() => {}} />
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId("canvas_controls_dropdown_docs"));
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
"https://docs.langflow.org",
|
||||
"_blank",
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId("canvas_controls_dropdown_shortcuts"));
|
||||
expect(mockNavigate).toHaveBeenCalledWith("/settings/shortcuts");
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByTestId("canvas_controls_dropdown_get_langflow_desktop"),
|
||||
);
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
"https://desktop.langflow.org",
|
||||
"_blank",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { HelpDropdownView } from "../HelpDropdownView";
|
||||
|
||||
jest.mock("@/components/ui/button", () => ({
|
||||
Button: ({ children, ...rest }) => <button {...rest}>{children}</button>,
|
||||
}));
|
||||
|
||||
jest.mock("@/components/ui/dropdown-menu", () => ({
|
||||
DropdownMenu: ({ children, open, onOpenChange }) => (
|
||||
<div data-testid="dropdown-menu" data-open={open}>
|
||||
<button
|
||||
onClick={() => onOpenChange?.(!open)}
|
||||
aria-expanded={open}
|
||||
aria-haspopup="true"
|
||||
type="button"
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
DropdownMenuContent: ({ children }) => (
|
||||
<div data-testid="dropdown-content">{children}</div>
|
||||
),
|
||||
DropdownMenuTrigger: ({ children }) => (
|
||||
<div data-testid="dropdown-trigger">{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock("@/components/ui/separator", () => ({
|
||||
Separator: () => <div data-testid="separator" />,
|
||||
}));
|
||||
|
||||
jest.mock("../DropdownControlButton", () => ({
|
||||
__esModule: true,
|
||||
default: ({ label, onClick, disabled, testId }) => (
|
||||
<button
|
||||
aria-label={label}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
data-testid={testId}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock("@/components/common/genericIconComponent", () => ({
|
||||
__esModule: true,
|
||||
default: ({ name }) => <span data-testid="icon">{name}</span>,
|
||||
}));
|
||||
|
||||
// Minimize utils import surface (prevents pulling heavy modules)
|
||||
jest.mock("@/utils/utils", () => ({
|
||||
__esModule: true,
|
||||
getOS: () => "macos",
|
||||
cn: (...args: Array<string>) => args.filter(Boolean).join(" "),
|
||||
}));
|
||||
|
||||
describe("HelpDropdownView", () => {
|
||||
it("calls provided handlers for each menu item", () => {
|
||||
const onOpenChange = jest.fn();
|
||||
const onToggleHelperLines = jest.fn();
|
||||
const navigateTo = jest.fn();
|
||||
const openLink = jest.fn();
|
||||
const urls = {
|
||||
docs: "https://docs",
|
||||
bugReport: "https://bugs",
|
||||
desktop: "https://desktop",
|
||||
};
|
||||
|
||||
render(
|
||||
<HelpDropdownView
|
||||
isOpen={true}
|
||||
onOpenChange={onOpenChange}
|
||||
helperLineEnabled={false}
|
||||
onToggleHelperLines={onToggleHelperLines}
|
||||
navigateTo={navigateTo}
|
||||
openLink={openLink}
|
||||
urls={urls}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId("canvas_controls_dropdown_docs"));
|
||||
expect(openLink).toHaveBeenCalledWith("https://docs");
|
||||
|
||||
fireEvent.click(screen.getByTestId("canvas_controls_dropdown_shortcuts"));
|
||||
expect(navigateTo).toHaveBeenCalledWith("/settings/shortcuts");
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByTestId("canvas_controls_dropdown_report_a_bug"),
|
||||
);
|
||||
expect(openLink).toHaveBeenCalledWith("https://bugs");
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByTestId("canvas_controls_dropdown_get_langflow_desktop"),
|
||||
);
|
||||
expect(openLink).toHaveBeenCalledWith("https://desktop");
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByTestId("canvas_controls_dropdown_enable_smart_guides"),
|
||||
);
|
||||
expect(onToggleHelperLines).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
import { ReactFlowState } from "@xyflow/react";
|
||||
import {
|
||||
formatZoomPercentage,
|
||||
getModifierKey,
|
||||
reactFlowSelector,
|
||||
} from "../utils/canvasUtils";
|
||||
|
||||
// Mock the getOS utility
|
||||
jest.mock("@/utils/utils", () => ({
|
||||
getOS: jest.fn(),
|
||||
}));
|
||||
|
||||
import { getOS } from "@/utils/utils";
|
||||
|
||||
const mockGetOS = getOS as jest.MockedFunction<typeof getOS>;
|
||||
|
||||
describe("canvasUtils", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("getModifierKey", () => {
|
||||
it("returns ⌘ for macOS", () => {
|
||||
mockGetOS.mockReturnValue("macos");
|
||||
expect(getModifierKey()).toBe("⌘");
|
||||
});
|
||||
|
||||
it("returns Ctrl for Windows", () => {
|
||||
mockGetOS.mockReturnValue("windows");
|
||||
expect(getModifierKey()).toBe("Ctrl");
|
||||
});
|
||||
|
||||
it("returns Ctrl for Linux", () => {
|
||||
mockGetOS.mockReturnValue("linux");
|
||||
expect(getModifierKey()).toBe("Ctrl");
|
||||
});
|
||||
|
||||
it("returns Ctrl for unknown OS", () => {
|
||||
mockGetOS.mockReturnValue("unknown");
|
||||
expect(getModifierKey()).toBe("Ctrl");
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatZoomPercentage", () => {
|
||||
it("formats zoom level 1 as 100%", () => {
|
||||
expect(formatZoomPercentage(1)).toBe("100%");
|
||||
});
|
||||
|
||||
it("formats zoom level 0.5 as 50%", () => {
|
||||
expect(formatZoomPercentage(0.5)).toBe("50%");
|
||||
});
|
||||
|
||||
it("formats zoom level 1.5 as 150%", () => {
|
||||
expect(formatZoomPercentage(1.5)).toBe("150%");
|
||||
});
|
||||
|
||||
it("formats zoom level 0.25 as 25%", () => {
|
||||
expect(formatZoomPercentage(0.25)).toBe("25%");
|
||||
});
|
||||
|
||||
it("formats zoom level 2.789 as 279%", () => {
|
||||
expect(formatZoomPercentage(2.789)).toBe("279%");
|
||||
});
|
||||
|
||||
it("rounds decimal values correctly", () => {
|
||||
expect(formatZoomPercentage(0.333)).toBe("33%");
|
||||
expect(formatZoomPercentage(0.666)).toBe("67%");
|
||||
expect(formatZoomPercentage(1.234)).toBe("123%");
|
||||
});
|
||||
|
||||
it("handles zero zoom level", () => {
|
||||
expect(formatZoomPercentage(0)).toBe("0%");
|
||||
});
|
||||
|
||||
it("handles very small zoom levels", () => {
|
||||
expect(formatZoomPercentage(0.001)).toBe("0%");
|
||||
expect(formatZoomPercentage(0.005)).toBe("1%");
|
||||
});
|
||||
|
||||
it("handles very large zoom levels", () => {
|
||||
expect(formatZoomPercentage(10)).toBe("1000%");
|
||||
expect(formatZoomPercentage(50.5)).toBe("5050%");
|
||||
});
|
||||
});
|
||||
|
||||
describe("reactFlowSelector", () => {
|
||||
const createMockReactFlowState = (
|
||||
overrides: Partial<ReactFlowState> = {},
|
||||
): ReactFlowState =>
|
||||
({
|
||||
nodesDraggable: true,
|
||||
nodesConnectable: true,
|
||||
elementsSelectable: true,
|
||||
transform: [0, 0, 1], // x, y, zoom
|
||||
minZoom: 0.1,
|
||||
maxZoom: 5,
|
||||
...overrides,
|
||||
}) as ReactFlowState;
|
||||
|
||||
it("returns correct isInteractive when all interaction modes are enabled", () => {
|
||||
const state = createMockReactFlowState({
|
||||
nodesDraggable: true,
|
||||
nodesConnectable: true,
|
||||
elementsSelectable: true,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.isInteractive).toBe(true);
|
||||
});
|
||||
|
||||
it("returns correct isInteractive when only nodesDraggable is enabled", () => {
|
||||
const state = createMockReactFlowState({
|
||||
nodesDraggable: true,
|
||||
nodesConnectable: false,
|
||||
elementsSelectable: false,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.isInteractive).toBe(true);
|
||||
});
|
||||
|
||||
it("returns correct isInteractive when only nodesConnectable is enabled", () => {
|
||||
const state = createMockReactFlowState({
|
||||
nodesDraggable: false,
|
||||
nodesConnectable: true,
|
||||
elementsSelectable: false,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.isInteractive).toBe(true);
|
||||
});
|
||||
|
||||
it("returns correct isInteractive when only elementsSelectable is enabled", () => {
|
||||
const state = createMockReactFlowState({
|
||||
nodesDraggable: false,
|
||||
nodesConnectable: false,
|
||||
elementsSelectable: true,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.isInteractive).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for isInteractive when all interaction modes are disabled", () => {
|
||||
const state = createMockReactFlowState({
|
||||
nodesDraggable: false,
|
||||
nodesConnectable: false,
|
||||
elementsSelectable: false,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.isInteractive).toBe(false);
|
||||
});
|
||||
|
||||
it("returns correct minZoomReached when zoom is at minimum", () => {
|
||||
const state = createMockReactFlowState({
|
||||
transform: [0, 0, 0.1], // zoom at minZoom
|
||||
minZoom: 0.1,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.minZoomReached).toBe(true);
|
||||
});
|
||||
|
||||
it("returns correct minZoomReached when zoom is below minimum", () => {
|
||||
const state = createMockReactFlowState({
|
||||
transform: [0, 0, 0.05], // zoom below minZoom
|
||||
minZoom: 0.1,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.minZoomReached).toBe(true);
|
||||
});
|
||||
|
||||
it("returns correct minZoomReached when zoom is above minimum", () => {
|
||||
const state = createMockReactFlowState({
|
||||
transform: [0, 0, 0.5], // zoom above minZoom
|
||||
minZoom: 0.1,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.minZoomReached).toBe(false);
|
||||
});
|
||||
|
||||
it("returns correct maxZoomReached when zoom is at maximum", () => {
|
||||
const state = createMockReactFlowState({
|
||||
transform: [0, 0, 5], // zoom at maxZoom
|
||||
maxZoom: 5,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.maxZoomReached).toBe(true);
|
||||
});
|
||||
|
||||
it("returns correct maxZoomReached when zoom is above maximum", () => {
|
||||
const state = createMockReactFlowState({
|
||||
transform: [0, 0, 10], // zoom above maxZoom
|
||||
maxZoom: 5,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.maxZoomReached).toBe(true);
|
||||
});
|
||||
|
||||
it("returns correct maxZoomReached when zoom is below maximum", () => {
|
||||
const state = createMockReactFlowState({
|
||||
transform: [0, 0, 2], // zoom below maxZoom
|
||||
maxZoom: 5,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.maxZoomReached).toBe(false);
|
||||
});
|
||||
|
||||
it("returns correct zoom level from transform", () => {
|
||||
const state = createMockReactFlowState({
|
||||
transform: [100, 200, 1.5], // x, y, zoom
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.zoom).toBe(1.5);
|
||||
});
|
||||
|
||||
it("handles edge case where zoom equals both min and max", () => {
|
||||
const state = createMockReactFlowState({
|
||||
transform: [0, 0, 1],
|
||||
minZoom: 1,
|
||||
maxZoom: 1,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
expect(result.minZoomReached).toBe(true);
|
||||
expect(result.maxZoomReached).toBe(true);
|
||||
expect(result.zoom).toBe(1);
|
||||
});
|
||||
|
||||
it("returns complete selector object with all properties", () => {
|
||||
const state = createMockReactFlowState({
|
||||
nodesDraggable: true,
|
||||
nodesConnectable: false,
|
||||
elementsSelectable: true,
|
||||
transform: [0, 0, 2],
|
||||
minZoom: 0.5,
|
||||
maxZoom: 4,
|
||||
});
|
||||
|
||||
const result = reactFlowSelector(state);
|
||||
|
||||
expect(result).toEqual({
|
||||
isInteractive: true,
|
||||
minZoomReached: false,
|
||||
maxZoomReached: false,
|
||||
zoom: 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
import {
|
||||
ControlButton,
|
||||
Panel,
|
||||
type ReactFlowState,
|
||||
useReactFlow,
|
||||
useStore,
|
||||
useStoreApi,
|
||||
} from "@xyflow/react";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import { shallow } from "zustand/shallow";
|
||||
import IconComponent from "@/components/common/genericIconComponent";
|
||||
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||
import useSaveFlow from "@/hooks/flows/use-save-flow";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import { cn } from "@/utils/utils";
|
||||
|
||||
type CustomControlButtonProps = {
|
||||
iconName: string;
|
||||
tooltipText: string;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
backgroundClasses?: string;
|
||||
iconClasses?: string;
|
||||
testId?: string;
|
||||
};
|
||||
|
||||
export const CustomControlButton = ({
|
||||
iconName,
|
||||
tooltipText,
|
||||
onClick,
|
||||
disabled,
|
||||
backgroundClasses,
|
||||
iconClasses,
|
||||
testId,
|
||||
}: CustomControlButtonProps): JSX.Element => {
|
||||
return (
|
||||
<ControlButton
|
||||
data-testid={testId}
|
||||
className="group !h-8 !w-8 rounded !p-0"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
title={testId?.replace(/_/g, " ")}
|
||||
>
|
||||
<ShadTooltip content={tooltipText} side="right">
|
||||
<div className={cn("rounded p-2.5", backgroundClasses)}>
|
||||
<IconComponent
|
||||
name={iconName}
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
"scale-150 text-muted-foreground group-hover:text-primary",
|
||||
iconClasses,
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
</ControlButton>
|
||||
);
|
||||
};
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
isInteractive: s.nodesDraggable || s.nodesConnectable || s.elementsSelectable,
|
||||
minZoomReached: s.transform[2] <= s.minZoom,
|
||||
maxZoomReached: s.transform[2] >= s.maxZoom,
|
||||
});
|
||||
|
||||
const CanvasControls = ({ children }) => {
|
||||
const store = useStoreApi();
|
||||
const { fitView, zoomIn, zoomOut } = useReactFlow();
|
||||
const { isInteractive, minZoomReached, maxZoomReached } = useStore(
|
||||
selector,
|
||||
shallow,
|
||||
);
|
||||
const saveFlow = useSaveFlow();
|
||||
const isLocked = useFlowStore(
|
||||
useShallow((state) => state.currentFlow?.locked),
|
||||
);
|
||||
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
|
||||
const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
|
||||
const setHelperLineEnabled = useFlowStore(
|
||||
(state) => state.setHelperLineEnabled,
|
||||
);
|
||||
const helperLineEnabled = useFlowStore((state) => state.helperLineEnabled);
|
||||
|
||||
useEffect(() => {
|
||||
store.setState({
|
||||
nodesDraggable: !isLocked,
|
||||
nodesConnectable: !isLocked,
|
||||
elementsSelectable: !isLocked,
|
||||
});
|
||||
}, [isLocked]);
|
||||
|
||||
const handleSaveFlow = useCallback(() => {
|
||||
const currentFlow = useFlowStore.getState().currentFlow;
|
||||
if (!currentFlow) return;
|
||||
const newFlow = cloneDeep(currentFlow);
|
||||
newFlow.locked = isInteractive;
|
||||
if (autoSaving) {
|
||||
saveFlow(newFlow);
|
||||
} else {
|
||||
setCurrentFlow(newFlow);
|
||||
}
|
||||
}, [isInteractive, autoSaving, saveFlow, setCurrentFlow]);
|
||||
|
||||
const onToggleInteractivity = useCallback(() => {
|
||||
store.setState({
|
||||
nodesDraggable: !isInteractive,
|
||||
nodesConnectable: !isInteractive,
|
||||
elementsSelectable: !isInteractive,
|
||||
});
|
||||
handleSaveFlow();
|
||||
}, [isInteractive, store, handleSaveFlow]);
|
||||
|
||||
const onToggleHelperLines = useCallback(() => {
|
||||
setHelperLineEnabled(!helperLineEnabled);
|
||||
}, [setHelperLineEnabled, helperLineEnabled]);
|
||||
|
||||
return (
|
||||
<Panel
|
||||
data-testid="canvas_controls"
|
||||
className="react-flow__controls !left-auto !m-2 flex !flex-col gap-1.5 rounded-md border border-border bg-background fill-foreground stroke-foreground p-0.5 text-primary [&>button]:border-0 [&>button]:bg-background hover:[&>button]:bg-accent"
|
||||
position="bottom-left"
|
||||
>
|
||||
{/* Zoom In */}
|
||||
<CustomControlButton
|
||||
iconName="ZoomIn"
|
||||
tooltipText="Zoom In"
|
||||
onClick={zoomIn}
|
||||
disabled={maxZoomReached}
|
||||
testId="zoom_in"
|
||||
/>
|
||||
{/* Zoom Out */}
|
||||
<CustomControlButton
|
||||
iconName="ZoomOut"
|
||||
tooltipText="Zoom Out"
|
||||
onClick={zoomOut}
|
||||
disabled={minZoomReached}
|
||||
testId="zoom_out"
|
||||
/>
|
||||
{/* Zoom To Fit */}
|
||||
<CustomControlButton
|
||||
iconName="maximize"
|
||||
tooltipText="Fit To Zoom"
|
||||
onClick={fitView}
|
||||
testId="fit_view"
|
||||
/>
|
||||
{children}
|
||||
{/* Lock/Unlock */}
|
||||
<CustomControlButton
|
||||
iconName={isInteractive ? "LockOpen" : "Lock"}
|
||||
tooltipText={isInteractive ? "Lock" : "Unlock"}
|
||||
onClick={onToggleInteractivity}
|
||||
backgroundClasses={isInteractive ? "" : "bg-destructive"}
|
||||
iconClasses={
|
||||
isInteractive ? "" : "text-primary-foreground dark:text-primary"
|
||||
}
|
||||
testId="lock_unlock"
|
||||
/>
|
||||
{/* Display Helper Lines */}
|
||||
<CustomControlButton
|
||||
iconName={helperLineEnabled ? "FoldHorizontal" : "UnfoldHorizontal"}
|
||||
tooltipText={
|
||||
helperLineEnabled ? "Hide Helper Lines" : "Show Helper Lines"
|
||||
}
|
||||
onClick={onToggleHelperLines}
|
||||
backgroundClasses={cn(helperLineEnabled && "bg-muted")}
|
||||
iconClasses={cn(helperLineEnabled && "text-muted-foreground")}
|
||||
testId="helper_lines"
|
||||
/>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
export default CanvasControls;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { ReactFlowState } from "@xyflow/react";
|
||||
import { getOS } from "@/utils/utils";
|
||||
|
||||
export const getModifierKey = (): string => {
|
||||
const os = getOS();
|
||||
return os === "macos" ? "⌘" : "Ctrl";
|
||||
};
|
||||
|
||||
export const formatZoomPercentage = (zoom: number): string =>
|
||||
`${Math.round(zoom * 100)}%`;
|
||||
|
||||
export const reactFlowSelector = (s: ReactFlowState) => ({
|
||||
isInteractive: s.nodesDraggable || s.nodesConnectable || s.elementsSelectable,
|
||||
minZoomReached: s.transform[2] <= s.minZoom,
|
||||
maxZoomReached: s.transform[2] >= s.maxZoom,
|
||||
zoom: s.transform[2],
|
||||
});
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import EditFlowSettings from "../index";
|
||||
|
||||
jest.mock("@/components/ui/input", () => ({
|
||||
Input: ({ ...props }) => <input {...props} />,
|
||||
}));
|
||||
jest.mock("@/components/ui/textarea", () => ({
|
||||
Textarea: ({ ...props }) => <textarea {...props} />,
|
||||
}));
|
||||
jest.mock("@/components/ui/switch", () => ({
|
||||
Switch: ({ checked, onCheckedChange, ...rest }) => (
|
||||
<input
|
||||
role="switch"
|
||||
aria-checked={!!checked}
|
||||
type="checkbox"
|
||||
checked={!!checked}
|
||||
onChange={(e) => onCheckedChange?.(e.target.checked)}
|
||||
{...rest}
|
||||
/>
|
||||
),
|
||||
}));
|
||||
jest.mock("@/components/common/genericIconComponent", () => ({
|
||||
__esModule: true,
|
||||
default: () => <span />,
|
||||
}));
|
||||
// Mock Radix Form to avoid context requirement
|
||||
jest.mock("@radix-ui/react-form", () => ({
|
||||
__esModule: true,
|
||||
Field: ({ children }) => <div>{children}</div>,
|
||||
Label: ({ children }) => <label>{children}</label>,
|
||||
Control: ({ children }) => <>{children}</>,
|
||||
Message: ({ children }) => <div>{children}</div>,
|
||||
Root: ({ children }) => <form>{children}</form>,
|
||||
}));
|
||||
|
||||
// Prevent importing utils that pull darkStore via side-effects
|
||||
jest.mock("@/utils/utils", () => ({
|
||||
__esModule: true,
|
||||
cn: (...args) => args.filter(Boolean).join(" "),
|
||||
}));
|
||||
|
||||
describe("EditFlowSettings", () => {
|
||||
it("validates name and shows messages", () => {
|
||||
const setName = jest.fn();
|
||||
render(
|
||||
<EditFlowSettings
|
||||
name=""
|
||||
description=""
|
||||
invalidNameList={["Taken"]}
|
||||
setName={setName}
|
||||
setDescription={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const nameInput = screen.getByTestId("input-flow-name");
|
||||
fireEvent.change(nameInput, { target: { value: "T" } });
|
||||
expect(setName).toHaveBeenCalledWith("T");
|
||||
|
||||
fireEvent.change(nameInput, { target: { value: "" } });
|
||||
expect(screen.getByText(/Please enter a name/)).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(nameInput, { target: { value: "Taken" } });
|
||||
expect(screen.getAllByText(/already exists/).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("submits with Enter in description without Shift", () => {
|
||||
const submitForm = jest.fn();
|
||||
render(
|
||||
<EditFlowSettings
|
||||
name="Flow"
|
||||
description="Desc"
|
||||
submitForm={submitForm}
|
||||
setName={jest.fn()}
|
||||
setDescription={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const desc = screen.getByTestId("input-flow-description");
|
||||
fireEvent.keyDown(desc, { key: "Enter", shiftKey: false });
|
||||
expect(submitForm).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("toggles lock switch", () => {
|
||||
const setLocked = jest.fn();
|
||||
render(
|
||||
<EditFlowSettings
|
||||
name="Flow"
|
||||
description="Desc"
|
||||
locked={false}
|
||||
setLocked={setLocked}
|
||||
setName={jest.fn()}
|
||||
setDescription={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
const sw = screen.getByTestId("lock-flow-switch");
|
||||
fireEvent.click(sw);
|
||||
expect(setLocked).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,13 +1,19 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import type { InputProps } from "../../../types/components";
|
||||
import { cn } from "../../../utils/utils";
|
||||
import { Input } from "../../ui/input";
|
||||
import { Textarea } from "../../ui/textarea";
|
||||
|
||||
export const EditFlowSettings: React.FC<
|
||||
InputProps & { submitForm?: () => void }
|
||||
InputProps & {
|
||||
submitForm?: () => void;
|
||||
locked?: boolean;
|
||||
setLocked?: (v: boolean) => void;
|
||||
}
|
||||
> = ({
|
||||
name,
|
||||
invalidNameList = [],
|
||||
|
|
@ -18,7 +24,13 @@ export const EditFlowSettings: React.FC<
|
|||
setName,
|
||||
setDescription,
|
||||
submitForm,
|
||||
}: InputProps & { submitForm?: () => void }): JSX.Element => {
|
||||
locked = false,
|
||||
setLocked,
|
||||
}: InputProps & {
|
||||
submitForm?: () => void;
|
||||
locked?: boolean;
|
||||
setLocked?: (v: boolean) => void;
|
||||
}): JSX.Element => {
|
||||
const [isMaxLength, setIsMaxLength] = useState(false);
|
||||
const [isMaxDescriptionLength, setIsMaxDescriptionLength] = useState(false);
|
||||
const [isMinLength, setIsMinLength] = useState(false);
|
||||
|
|
@ -110,6 +122,7 @@ export const EditFlowSettings: React.FC<
|
|||
onDoubleClickCapture={handleFocus}
|
||||
data-testid="input-flow-name"
|
||||
autoFocus
|
||||
disabled={locked}
|
||||
/>
|
||||
</Form.Control>
|
||||
) : (
|
||||
|
|
@ -128,7 +141,7 @@ export const EditFlowSettings: React.FC<
|
|||
</Form.Message>
|
||||
</Form.Field>
|
||||
<Form.Field name="description">
|
||||
<div className="edit-flow-arrangement mt-3">
|
||||
<div className="edit-flow-arrangement mt-2">
|
||||
<Form.Label className="text-mmd font-medium">
|
||||
Description{setDescription ? "" : ":"}
|
||||
</Form.Label>
|
||||
|
|
@ -150,6 +163,7 @@ export const EditFlowSettings: React.FC<
|
|||
maxLength={descriptionMaxLength}
|
||||
onDoubleClickCapture={handleFocus}
|
||||
onKeyDown={handleDescriptionKeyDown}
|
||||
disabled={locked}
|
||||
/>
|
||||
</Form.Control>
|
||||
) : (
|
||||
|
|
@ -165,6 +179,19 @@ export const EditFlowSettings: React.FC<
|
|||
<Form.Message match="valueMissing" className="field-invalid">
|
||||
Please enter a description
|
||||
</Form.Message>
|
||||
<div className="flex items-center gap-2 mt-3">
|
||||
<ForwardedIconComponent
|
||||
name={locked ? "Lock" : "Unlock"}
|
||||
className="text-muted-foreground !w-5 !h-5"
|
||||
/>
|
||||
<Form.Label className="text-mmd font-medium">Lock Flow</Form.Label>
|
||||
<Switch
|
||||
checked={!!locked}
|
||||
onCheckedChange={(v) => setLocked?.(v)}
|
||||
className="data-[state=checked]:bg-primary ml-auto"
|
||||
data-testid="lock-flow-switch"
|
||||
/>
|
||||
</div>
|
||||
</Form.Field>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import FlowSettingsComponent from "../index";
|
||||
|
||||
jest.mock("@/components/ui/button", () => ({
|
||||
Button: ({ children, loading, ...rest }) => (
|
||||
<button {...rest}>{children}</button>
|
||||
),
|
||||
}));
|
||||
|
||||
// Simplify Radix Form to a native form that respects onSubmit
|
||||
jest.mock("@radix-ui/react-form", () => ({
|
||||
__esModule: true,
|
||||
Root: React.forwardRef<HTMLFormElement, any>(
|
||||
({ children, onSubmit }, ref) => (
|
||||
<form onSubmit={onSubmit} ref={ref}>
|
||||
{children}
|
||||
</form>
|
||||
),
|
||||
),
|
||||
Submit: ({ asChild, children }) => {
|
||||
if (asChild && React.isValidElement(children)) {
|
||||
return React.cloneElement(children as any, { type: "submit" });
|
||||
}
|
||||
return <button type="submit">Submit</button>;
|
||||
},
|
||||
}));
|
||||
|
||||
const mockSave = jest.fn();
|
||||
jest.mock("@/hooks/flows/use-save-flow", () => ({
|
||||
__esModule: true,
|
||||
default: () => mockSave,
|
||||
}));
|
||||
|
||||
let mockSetSuccessData = jest.fn();
|
||||
jest.mock("@/stores/alertStore", () => ({
|
||||
__esModule: true,
|
||||
default: (sel) => sel({ setSuccessData: mockSetSuccessData }),
|
||||
}));
|
||||
|
||||
let mockSetCurrentFlow = jest.fn();
|
||||
jest.mock("@/stores/flowStore", () => ({
|
||||
__esModule: true,
|
||||
default: (sel) =>
|
||||
sel({
|
||||
currentFlow: {
|
||||
id: "1",
|
||||
name: "Flow",
|
||||
description: "Desc",
|
||||
locked: false,
|
||||
},
|
||||
setCurrentFlow: mockSetCurrentFlow,
|
||||
}),
|
||||
}));
|
||||
|
||||
let mockAutoSaving = false;
|
||||
let mockFlows: Array<{ name: string }> = [];
|
||||
jest.mock("@/stores/flowsManagerStore", () => ({
|
||||
__esModule: true,
|
||||
default: (sel) => sel({ autoSaving: mockAutoSaving, flows: mockFlows }),
|
||||
}));
|
||||
|
||||
// Mock EditFlowSettings to expose simple controls that call the provided setters
|
||||
jest.mock("@/components/core/editFlowSettingsComponent", () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
setName,
|
||||
setDescription,
|
||||
setLocked,
|
||||
}: {
|
||||
setName?: (v: string) => void;
|
||||
setDescription?: (v: string) => void;
|
||||
setLocked?: (v: boolean) => void;
|
||||
}) => (
|
||||
<div>
|
||||
<button data-testid="set-name-new" onClick={() => setName?.("New Name")}>
|
||||
set name
|
||||
</button>
|
||||
<button data-testid="set-name-taken" onClick={() => setName?.("Taken")}>
|
||||
set taken
|
||||
</button>
|
||||
<button
|
||||
data-testid="set-desc-new"
|
||||
onClick={() => setDescription?.("New Desc")}
|
||||
>
|
||||
set desc
|
||||
</button>
|
||||
<button data-testid="toggle-lock" onClick={() => setLocked?.(true)}>
|
||||
toggle lock
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("FlowSettingsComponent", () => {
|
||||
const baseFlow = {
|
||||
id: "1",
|
||||
name: "Flow",
|
||||
description: "Desc",
|
||||
locked: false,
|
||||
} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockAutoSaving = false;
|
||||
mockFlows = [{ name: "Flow" }, { name: "Other" }];
|
||||
mockSetSuccessData = jest.fn();
|
||||
mockSetCurrentFlow = jest.fn();
|
||||
});
|
||||
|
||||
it("renders and disables save when no changes", () => {
|
||||
render(<FlowSettingsComponent flowData={baseFlow} open close={() => {}} />);
|
||||
const saveBtn = screen.getByTestId("save-flow-settings");
|
||||
expect(saveBtn).toBeDisabled();
|
||||
});
|
||||
|
||||
it("enables save when name changes and autoSaving true triggers saveFlow and success", async () => {
|
||||
mockAutoSaving = true;
|
||||
mockSave.mockResolvedValueOnce(undefined);
|
||||
const onClose = jest.fn();
|
||||
|
||||
render(<FlowSettingsComponent flowData={baseFlow} open close={onClose} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("set-name-new"));
|
||||
const saveBtn = screen.getByTestId("save-flow-settings");
|
||||
expect(saveBtn).not.toBeDisabled();
|
||||
|
||||
fireEvent.click(saveBtn);
|
||||
|
||||
// Wait microtask queue to resolve promise chain
|
||||
await Promise.resolve();
|
||||
|
||||
expect(mockSave).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: "New Name" }),
|
||||
);
|
||||
expect(mockSetSuccessData).toHaveBeenCalledWith({
|
||||
title: "Changes saved successfully",
|
||||
});
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("non-autoSaving path sets current flow and closes", () => {
|
||||
mockAutoSaving = false;
|
||||
const onClose = jest.fn();
|
||||
|
||||
render(<FlowSettingsComponent flowData={baseFlow} open close={onClose} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("set-desc-new"));
|
||||
const saveBtn = screen.getByTestId("save-flow-settings");
|
||||
expect(saveBtn).not.toBeDisabled();
|
||||
fireEvent.click(saveBtn);
|
||||
|
||||
expect(mockSetCurrentFlow).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ description: "New Desc" }),
|
||||
);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("prevents saving when name is taken", () => {
|
||||
mockFlows = [{ name: "Taken" }, { name: "Flow" }];
|
||||
|
||||
render(<FlowSettingsComponent flowData={baseFlow} open close={() => {}} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("set-name-taken"));
|
||||
expect(screen.getByTestId("save-flow-settings")).toBeDisabled();
|
||||
});
|
||||
|
||||
it("clicking cancel calls close", () => {
|
||||
const onClose = jest.fn();
|
||||
render(<FlowSettingsComponent flowData={baseFlow} open close={onClose} />);
|
||||
fireEvent.click(screen.getByTestId("cancel-flow-settings"));
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -9,15 +9,54 @@ import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
|||
import type { FlowType } from "@/types/flow";
|
||||
import EditFlowSettings from "../editFlowSettingsComponent";
|
||||
|
||||
export default function FlowSettingsComponent({
|
||||
flowData,
|
||||
close,
|
||||
open,
|
||||
}: {
|
||||
type FlowSettingsComponentProps = {
|
||||
flowData?: FlowType;
|
||||
close: () => void;
|
||||
open: boolean;
|
||||
}): JSX.Element {
|
||||
};
|
||||
|
||||
const updateFlowWithFormValues = (
|
||||
baseFlow: FlowType,
|
||||
newName: string,
|
||||
newDescription: string,
|
||||
newLocked: boolean,
|
||||
): FlowType => {
|
||||
const newFlow = cloneDeep(baseFlow);
|
||||
newFlow.name = newName;
|
||||
newFlow.description = newDescription;
|
||||
newFlow.locked = newLocked;
|
||||
return newFlow;
|
||||
};
|
||||
|
||||
const buildInvalidNameList = (
|
||||
allFlows: FlowType[] | undefined,
|
||||
currentFlowName: string | undefined,
|
||||
): string[] => {
|
||||
if (!allFlows) return [];
|
||||
const names = allFlows.map((f) => f?.name ?? "");
|
||||
return names.filter((n) => n !== (currentFlowName ?? ""));
|
||||
};
|
||||
|
||||
const isSaveDisabled = (
|
||||
flow: FlowType | undefined,
|
||||
invalidNameList: string[],
|
||||
name: string,
|
||||
description: string,
|
||||
locked: boolean,
|
||||
): boolean => {
|
||||
if (!flow) return true;
|
||||
const isNameChangedAndValid =
|
||||
!invalidNameList.includes(name) && flow.name !== name;
|
||||
const isDescriptionChanged = flow.description !== description;
|
||||
const isLockedChanged = flow.locked !== locked;
|
||||
return !(isNameChangedAndValid || isDescriptionChanged || isLockedChanged);
|
||||
};
|
||||
|
||||
const FlowSettingsComponent = ({
|
||||
flowData,
|
||||
close,
|
||||
open,
|
||||
}: FlowSettingsComponentProps): JSX.Element => {
|
||||
const saveFlow = useSaveFlow();
|
||||
const currentFlow = useFlowStore((state) =>
|
||||
flowData ? undefined : state.currentFlow,
|
||||
|
|
@ -28,6 +67,7 @@ export default function FlowSettingsComponent({
|
|||
const flow = flowData ?? currentFlow;
|
||||
const [name, setName] = useState(flow?.name ?? "");
|
||||
const [description, setDescription] = useState(flow?.description ?? "");
|
||||
const [locked, setLocked] = useState<boolean>(flow?.locked ?? false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [disableSave, setDisableSave] = useState(true);
|
||||
const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
|
||||
|
|
@ -36,15 +76,14 @@ export default function FlowSettingsComponent({
|
|||
useEffect(() => {
|
||||
setName(flow?.name ?? "");
|
||||
setDescription(flow?.description ?? "");
|
||||
setLocked(flow?.locked ?? false);
|
||||
}, [flow?.name, flow?.description, flow?.endpoint_name, open]);
|
||||
|
||||
function handleSubmit(event?: React.FormEvent<HTMLFormElement>): void {
|
||||
if (event) event.preventDefault();
|
||||
setIsSaving(true);
|
||||
if (!flow) return;
|
||||
const newFlow = cloneDeep(flow);
|
||||
newFlow.name = name;
|
||||
newFlow.description = description;
|
||||
const newFlow = updateFlowWithFormValues(flow, name, description, locked);
|
||||
|
||||
if (autoSaving) {
|
||||
saveFlow(newFlow)
|
||||
|
|
@ -70,25 +109,12 @@ export default function FlowSettingsComponent({
|
|||
const [nameLists, setNameList] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (flows) {
|
||||
const tempNameList: string[] = [];
|
||||
flows.forEach((flow: FlowType) => {
|
||||
tempNameList.push(flow?.name ?? "");
|
||||
});
|
||||
setNameList(tempNameList.filter((name) => name !== (flow?.name ?? "")));
|
||||
}
|
||||
setNameList(buildInvalidNameList(flows, flow?.name));
|
||||
}, [flows]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
(!nameLists.includes(name) && flow?.name !== name) ||
|
||||
flow?.description !== description
|
||||
) {
|
||||
setDisableSave(false);
|
||||
} else {
|
||||
setDisableSave(true);
|
||||
}
|
||||
}, [nameLists, flow, description, name]);
|
||||
setDisableSave(isSaveDisabled(flow, nameLists, name, description, locked));
|
||||
}, [nameLists, flow, description, name, locked]);
|
||||
return (
|
||||
<Form.Root onSubmit={handleSubmit} ref={formRef}>
|
||||
<div className="flex flex-col gap-4">
|
||||
|
|
@ -100,6 +126,8 @@ export default function FlowSettingsComponent({
|
|||
setName={setName}
|
||||
setDescription={setDescription}
|
||||
submitForm={submitForm}
|
||||
locked={locked}
|
||||
setLocked={setLocked}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
|
|
@ -127,4 +155,6 @@ export default function FlowSettingsComponent({
|
|||
</div>
|
||||
</Form.Root>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default FlowSettingsComponent;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import LogCanvasControls from "../index";
|
||||
|
||||
jest.mock("@/modals/flowLogsModal", () => ({
|
||||
__esModule: true,
|
||||
default: ({ children }) => <div data-testid="logs-modal">{children}</div>,
|
||||
}));
|
||||
jest.mock("@/components/common/genericIconComponent", () => ({
|
||||
__esModule: true,
|
||||
default: () => <span data-testid="icon" />,
|
||||
}));
|
||||
jest.mock("@xyflow/react", () => ({
|
||||
Panel: ({ children, ...rest }) => <div {...rest}>{children}</div>,
|
||||
}));
|
||||
jest.mock("@/components/ui/button", () => ({
|
||||
Button: ({ children, ...rest }) => <button {...rest}>{children}</button>,
|
||||
}));
|
||||
|
||||
describe("LogCanvasControls", () => {
|
||||
it("renders panel and button", () => {
|
||||
render(<LogCanvasControls />);
|
||||
expect(screen.getByTestId("canvas_controls")).toBeInTheDocument();
|
||||
expect(screen.getByText("Logs")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -7,8 +7,8 @@ const LogCanvasControls = () => {
|
|||
return (
|
||||
<Panel
|
||||
data-testid="canvas_controls"
|
||||
className="react-flow__controls !m-2 rounded-md"
|
||||
position="bottom-right"
|
||||
className="react-flow__controls !m-2 rounded-md !left-auto "
|
||||
position="bottom-left"
|
||||
>
|
||||
<FlowLogsModal>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -1104,5 +1104,7 @@ export const TWITTER_URL = "https://x.com/langflow_ai";
|
|||
export const DOCS_URL = "https://docs.langflow.org";
|
||||
export const DATASTAX_DOCS_URL =
|
||||
"https://docs.datastax.com/en/langflow/index.html";
|
||||
export const DESKTOP_URL = "https://www.langflow.org/desktop";
|
||||
export const BUG_REPORT_URL = "https://github.com/langflow-ai/langflow/issues";
|
||||
|
||||
export const UUID_PARSING_ERROR = "uuid_parsing";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { Background, Panel } from "@xyflow/react";
|
||||
import { memo } from "react";
|
||||
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||
import CanvasControls, {
|
||||
CustomControlButton,
|
||||
} from "@/components/core/canvasControlsComponent";
|
||||
import {
|
||||
default as ForwardedIconComponent,
|
||||
default as IconComponent,
|
||||
} from "@/components/common/genericIconComponent";
|
||||
import CanvasControls from "@/components/core/canvasControlsComponent/CanvasControls";
|
||||
import LogCanvasControls from "@/components/core/logCanvasControlsComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SidebarTrigger } from "@/components/ui/sidebar";
|
||||
import { cn } from "@/utils/utils";
|
||||
|
||||
|
|
@ -29,10 +31,14 @@ export const MemoizedCanvasControls = memo(
|
|||
shadowBoxHeight,
|
||||
}: MemoizedCanvasControlsProps) => (
|
||||
<CanvasControls>
|
||||
<CustomControlButton
|
||||
iconName="sticky-note"
|
||||
tooltipText="Add Note"
|
||||
onClick={() => {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
data-testid="add_note"
|
||||
className="group flex items-center justify-center px-2 rounded-none"
|
||||
title="Add sticky note"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsAddingNote(true);
|
||||
const shadowBox = document.getElementById("shadow-box");
|
||||
if (shadowBox) {
|
||||
|
|
@ -41,8 +47,12 @@ export const MemoizedCanvasControls = memo(
|
|||
shadowBox.style.top = `${position.y - shadowBoxHeight / 2}px`;
|
||||
}
|
||||
}}
|
||||
testId="add_note"
|
||||
/>
|
||||
>
|
||||
<IconComponent
|
||||
name="sticky-note"
|
||||
className="!h-5 !w-5 text-muted-foreground group-hover:text-primary"
|
||||
/>
|
||||
</Button>
|
||||
</CanvasControls>
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import {
|
||||
MemoizedCanvasControls,
|
||||
MemoizedLogCanvasControls,
|
||||
MemoizedSidebarTrigger,
|
||||
} from "../MemoizedComponents";
|
||||
|
||||
jest.mock("@/components/core/canvasControlsComponent/CanvasControls", () => ({
|
||||
__esModule: true,
|
||||
default: ({ children }) => (
|
||||
<div data-testid="canvas-controls">{children}</div>
|
||||
),
|
||||
}));
|
||||
jest.mock("@/components/common/genericIconComponent", () => ({
|
||||
__esModule: true,
|
||||
default: ({ name }) => <span data-testid="icon">{name}</span>,
|
||||
}));
|
||||
jest.mock("@/components/ui/button", () => ({
|
||||
Button: ({ children, ...rest }) => <button {...rest}>{children}</button>,
|
||||
}));
|
||||
jest.mock("@xyflow/react", () => ({
|
||||
Panel: ({ children, ...rest }) => (
|
||||
<div data-testid="panel" {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
jest.mock("@/components/core/logCanvasControlsComponent", () => ({
|
||||
__esModule: true,
|
||||
default: () => <div data-testid="log-controls" />,
|
||||
}));
|
||||
jest.mock("@/components/ui/sidebar", () => ({
|
||||
SidebarTrigger: ({ children, ...rest }) => (
|
||||
<button {...rest}>{children}</button>
|
||||
),
|
||||
}));
|
||||
// Avoid utils importing darkStore
|
||||
jest.mock("@/utils/utils", () => ({
|
||||
__esModule: true,
|
||||
cn: (...args) => args.filter(Boolean).join(" "),
|
||||
}));
|
||||
|
||||
describe("MemoizedComponents", () => {
|
||||
it("clicking add note sets state and positions shadow box", () => {
|
||||
const setIsAddingNote = jest.fn();
|
||||
document.body.innerHTML = '<div id="shadow-box"></div>';
|
||||
render(
|
||||
<MemoizedCanvasControls
|
||||
setIsAddingNote={setIsAddingNote}
|
||||
position={{ x: 100, y: 200 }}
|
||||
shadowBoxWidth={40}
|
||||
shadowBoxHeight={20}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByTestId("add_note"));
|
||||
expect(setIsAddingNote).toHaveBeenCalledWith(true);
|
||||
const box = document.getElementById("shadow-box")! as HTMLDivElement;
|
||||
expect(box.style.display).toBe("block");
|
||||
expect(box.style.left).toBe("80px");
|
||||
expect(box.style.top).toBe("190px");
|
||||
});
|
||||
|
||||
it("renders sidebar trigger and log controls", () => {
|
||||
render(<MemoizedSidebarTrigger />);
|
||||
expect(screen.getByText("Components")).toBeInTheDocument();
|
||||
render(<MemoizedLogCanvasControls />);
|
||||
expect(screen.getByTestId("log-controls")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import {
|
||||
type Connection,
|
||||
type Edge,
|
||||
type NodeChange,
|
||||
type OnNodeDrag,
|
||||
type OnSelectionChangeParams,
|
||||
ReactFlow,
|
||||
reconnectEdge,
|
||||
type SelectionDragHandler,
|
||||
} from "@xyflow/react";
|
||||
import { AnimatePresence } from "framer-motion";
|
||||
import _, { cloneDeep } from "lodash";
|
||||
import {
|
||||
type KeyboardEvent,
|
||||
|
|
@ -34,7 +34,7 @@ import useAutoSaveFlow from "@/hooks/flows/use-autosave-flow";
|
|||
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||
import { useAddComponent } from "@/hooks/use-add-component";
|
||||
import { nodeColorsName } from "@/utils/styleUtils";
|
||||
import { cn, isSupportedNodeTypes } from "@/utils/utils";
|
||||
import { isSupportedNodeTypes } from "@/utils/utils";
|
||||
import GenericNode from "../../../../CustomNodes/GenericNode";
|
||||
import {
|
||||
INVALID_SELECTION_ERROR_ALERT,
|
||||
|
|
|
|||
|
|
@ -84,13 +84,15 @@ test(
|
|||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByText("Chat Input").first().click();
|
||||
await page.waitForSelector('[data-testid="more-options-modal"]', {
|
||||
|
|
|
|||
|
|
@ -140,13 +140,17 @@ test(
|
|||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await renameFlow(page, { flowName: randomFlowName });
|
||||
|
||||
await page.waitForSelector('[data-testid="icon-ChevronLeft"]', {
|
||||
|
|
@ -215,13 +219,17 @@ test(
|
|||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await renameFlow(page, { flowName: secondRandomFlowName });
|
||||
|
||||
await page.waitForSelector('[data-testid="icon-ChevronLeft"]', {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ test(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,12 @@ test(
|
|||
|
||||
await page.getByTestId("blank-flow").click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="zoom_out"]', {
|
||||
timeout: 3000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("sidebar-custom-component-button").click();
|
||||
|
||||
|
|
|
|||
156
src/frontend/tests/core/features/flow-lock.spec.ts
Normal file
156
src/frontend/tests/core/features/flow-lock.spec.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
|
||||
test.describe("Flow Lock Feature", () => {
|
||||
test(
|
||||
"should lock and unlock a flow and verify UI changes",
|
||||
{ tag: ["@release", "@api"] },
|
||||
async ({ page }) => {
|
||||
await awaitBootstrapTest(page);
|
||||
|
||||
// Navigate to templates and select a flow to work with
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
|
||||
// Wait for the flow to load
|
||||
await page.waitForSelector('[data-testid="icon-ChevronLeft"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
// Verify initially the flow is not locked (no lock icon should be visible)
|
||||
const initialLockIcon = page.locator(
|
||||
'[data-testid="menu_bar_display"] [data-testid="icon-Lock"]',
|
||||
);
|
||||
await expect(initialLockIcon).toHaveCount(0);
|
||||
|
||||
// Open flow settings by clicking on the flow name
|
||||
await page.getByTestId("menu_bar_display").click();
|
||||
|
||||
// Wait for the settings modal to open
|
||||
await page.waitForSelector('[data-testid="lock-flow-switch"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
// Verify the lock switch is initially unchecked
|
||||
const lockSwitch = page.getByTestId("lock-flow-switch");
|
||||
await expect(lockSwitch).toBeVisible();
|
||||
await expect(lockSwitch).toHaveAttribute("data-state", "unchecked");
|
||||
|
||||
// Verify that name and description inputs are enabled when not locked
|
||||
const nameInput = page.getByTestId("input-flow-name");
|
||||
const descriptionInput = page.getByTestId("input-flow-description");
|
||||
|
||||
await expect(nameInput).toBeEnabled();
|
||||
await expect(descriptionInput).toBeEnabled();
|
||||
|
||||
await lockSwitch.click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const stateAfterClick = await lockSwitch.getAttribute("data-state");
|
||||
if (stateAfterClick !== "checked") {
|
||||
await lockSwitch.click();
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
await expect(lockSwitch).toHaveAttribute("data-state", "checked");
|
||||
|
||||
// Verify that inputs become disabled when locked
|
||||
await expect(nameInput).toBeDisabled();
|
||||
await expect(descriptionInput).toBeDisabled();
|
||||
|
||||
// Save the settings by clicking the save button
|
||||
const saveButton = page.getByTestId("save-flow-settings");
|
||||
|
||||
if (await saveButton.isEnabled({ timeout: 3000 })) {
|
||||
await saveButton.click();
|
||||
}
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Wait for the modal to close by waiting for the popover to be detached
|
||||
await page.waitForSelector('[role="dialog"]', {
|
||||
state: "detached",
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// Verify lock icon now appears in the flow header
|
||||
const lockIconInHeader = page
|
||||
.locator('[data-testid="menu_bar_display"]')
|
||||
.locator('[data-testid="icon-Lock"]');
|
||||
await expect(lockIconInHeader).toBeVisible();
|
||||
|
||||
// Try to open settings again to unlock
|
||||
await page.getByTestId("menu_bar_display").click();
|
||||
|
||||
// Wait for the settings modal to open again
|
||||
await page.waitForSelector('[data-testid="lock-flow-switch"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
// Verify the switch is checked (locked state persisted)
|
||||
await expect(lockSwitch).toHaveAttribute("data-state", "checked");
|
||||
|
||||
// Verify inputs are still disabled
|
||||
await expect(nameInput).toBeDisabled();
|
||||
await expect(descriptionInput).toBeDisabled();
|
||||
|
||||
// Unlock the flow
|
||||
await lockSwitch.focus();
|
||||
await lockSwitch.press("Space");
|
||||
|
||||
// Verify the switch is now unchecked
|
||||
await expect(lockSwitch).toHaveAttribute("data-state", "unchecked");
|
||||
|
||||
// Verify that inputs become enabled again when unlocked
|
||||
await expect(nameInput).toBeEnabled();
|
||||
await expect(descriptionInput).toBeEnabled();
|
||||
|
||||
// Save the unlocked state by clicking the save button
|
||||
await page.getByTestId("save-flow-settings").isEnabled({ timeout: 3000 });
|
||||
await page.getByTestId("save-flow-settings").click();
|
||||
|
||||
// Wait for the modal to close by waiting for the popover to be detached
|
||||
await page.waitForSelector('[role="dialog"]', {
|
||||
state: "detached",
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// Verify lock icon is no longer visible in the flow header
|
||||
await expect(lockIconInHeader).toHaveCount(0);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"should show correct lock/unlock icon in settings based on state",
|
||||
{ tag: ["@release", "@api"] },
|
||||
async ({ page }) => {
|
||||
await awaitBootstrapTest(page);
|
||||
|
||||
// Navigate to templates and select a flow
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
|
||||
// Wait for the flow to load
|
||||
await page.waitForSelector('[data-testid="icon-ChevronLeft"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
// Open flow settings
|
||||
await page.getByTestId("menu_bar_display").click();
|
||||
await page.waitForSelector('[data-testid="lock-flow-switch"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
// Initially should show unlock icon (flow is unlocked)
|
||||
const unlockIcon = page.locator('[data-testid="icon-Unlock"]');
|
||||
await expect(unlockIcon).toBeVisible();
|
||||
|
||||
// Lock the flow
|
||||
const lockSwitch = page.getByTestId("lock-flow-switch");
|
||||
await lockSwitch.click();
|
||||
|
||||
// Should now show lock icon
|
||||
const lockIcon = page.locator('[data-testid="icon-Lock"]');
|
||||
await expect(lockIcon).toBeVisible();
|
||||
await expect(unlockIcon).toHaveCount(0);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
@ -44,7 +44,10 @@ test(
|
|||
await page.getByTestId("dropdown_str_model_name").click();
|
||||
await page.getByTestId("gpt-4o-1-option").click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="button_run_chat output"]', {
|
||||
timeout: 3000,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
import { adjustScreenView } from "../../utils/adjust-screen-view";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
import { initialGPTsetup } from "../../utils/initialGPTsetup";
|
||||
|
||||
|
|
@ -27,7 +26,10 @@ test(
|
|||
await page.getByTestId("add-component-button-openai").last().click();
|
||||
});
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page, {
|
||||
skipAdjustScreenView: true,
|
||||
|
|
|
|||
|
|
@ -33,8 +33,10 @@ test(
|
|||
await page.getByTestId("add-component-button-chat-output").click();
|
||||
});
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("chat input");
|
||||
|
|
|
|||
|
|
@ -48,12 +48,14 @@ test.describe("save component tests", () => {
|
|||
if (elementCount > 0) {
|
||||
expect(true).toBeTruthy();
|
||||
}
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
// Log button element
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await zoomOut(page, 2);
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("title-Agent Initializer").click({
|
||||
modifiers: ["Control"],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,8 +26,10 @@ test(
|
|||
targetPosition: { x: 0, y: 0 },
|
||||
});
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await zoomOut(page, 3);
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
//second component
|
||||
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
|
|
@ -75,9 +77,12 @@ test(
|
|||
await updateOldComponents(page);
|
||||
await removeOldApiKeys(page);
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await zoomOut(page, 2);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
//connection 1
|
||||
await page
|
||||
|
|
@ -101,7 +106,10 @@ test(
|
|||
.getByTestId("handle-chatoutput-noshownode-inputs-target")
|
||||
.click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("textarea_str_input_value").first().fill(",");
|
||||
|
||||
|
|
@ -142,8 +150,11 @@ class CustomComponent(Component):
|
|||
`;
|
||||
|
||||
await page.getByTestId("sidebar-custom-component-button").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("title-Custom Component").first().click();
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ test("check if tweaks are updating when someothing on the flow changes", async (
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
|
|
@ -85,6 +86,7 @@ test("check if tweaks are updating when someothing on the flow changes", async (
|
|||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page.getByTestId("popover-anchor-input-collection_name").click();
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-collection_name")
|
||||
|
|
|
|||
|
|
@ -109,7 +109,9 @@ test(
|
|||
timeout: 3000,
|
||||
});
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page.waitForSelector('[data-testid="fit_view"]', { timeout: 30000 });
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await renameFlow(page, { flowName: userAFlowName });
|
||||
|
||||
|
|
|
|||
|
|
@ -24,10 +24,11 @@ withEventDeliveryModes(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByTestId("template-custom-component-generator").click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="dropdown_str_model_name"]', {
|
||||
timeout: 5000,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ import { expect, test } from "@playwright/test";
|
|||
import * as dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
import { getAllResponseMessage } from "../../utils/get-all-response-message";
|
||||
import { initialGPTsetup } from "../../utils/initialGPTsetup";
|
||||
import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal";
|
||||
import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes";
|
||||
|
||||
withEventDeliveryModes(
|
||||
|
|
@ -27,10 +25,12 @@ withEventDeliveryModes(
|
|||
await page
|
||||
.getByRole("heading", { name: "Financial Report Parser" })
|
||||
.click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
import * as dotenv from "dotenv";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
import { getAllResponseMessage } from "../../utils/get-all-response-message";
|
||||
import { initialGPTsetup } from "../../utils/initialGPTsetup";
|
||||
import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal";
|
||||
import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes";
|
||||
|
||||
withEventDeliveryModes(
|
||||
|
|
@ -31,10 +28,12 @@ withEventDeliveryModes(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Gmail Agent" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
|
|
|
|||
|
|
@ -31,11 +31,14 @@ withEventDeliveryModes(
|
|||
.last()
|
||||
.click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,10 +29,12 @@ test(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Instagram Copywriter" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,10 +29,12 @@ withEventDeliveryModes(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Market Research" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
await page
|
||||
|
|
|
|||
|
|
@ -28,10 +28,12 @@ withEventDeliveryModes(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "News Aggregator" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page, {
|
||||
skipAdjustScreenView: true,
|
||||
|
|
|
|||
|
|
@ -28,10 +28,12 @@ withEventDeliveryModes(
|
|||
await page
|
||||
.getByRole("heading", { name: "Portfolio Website Code Generator" })
|
||||
.click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page, {
|
||||
skipAdjustScreenView: true,
|
||||
|
|
|
|||
|
|
@ -32,10 +32,12 @@ withEventDeliveryModes(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Price Deal Finder" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page, {
|
||||
skipAdjustScreenView: true,
|
||||
|
|
|
|||
|
|
@ -25,10 +25,12 @@ withEventDeliveryModes(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Prompt Chaining" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,10 +24,12 @@ withEventDeliveryModes(
|
|||
await page
|
||||
.getByRole("heading", { name: "Research Translation Loop" })
|
||||
.click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page, {
|
||||
skipAdjustScreenView: true,
|
||||
|
|
|
|||
|
|
@ -25,10 +25,12 @@ withEventDeliveryModes(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "SEO Keyword Generator" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,12 @@ withEventDeliveryModes(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "SaaS Pricing" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,6 @@ withEventDeliveryModes(
|
|||
.isVisible();
|
||||
|
||||
const textAnalysis = await page.locator(".markdown").last().textContent();
|
||||
expect(textAnalysis?.length).toBeGreaterThan(100);
|
||||
expect(textAnalysis?.length).toBeGreaterThan(50);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -31,10 +31,12 @@ withEventDeliveryModes(
|
|||
.getByRole("heading", { name: "Travel Planning Agents" })
|
||||
.last()
|
||||
.click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,10 +27,12 @@ withEventDeliveryModes(
|
|||
await page
|
||||
.getByRole("heading", { name: "Twitter Thread Generator" })
|
||||
.click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,11 +28,14 @@ withEventDeliveryModes(
|
|||
.getByRole("heading", { name: "Vector Store RAG" })
|
||||
.first()
|
||||
.click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[title="fit view"]', {
|
||||
timeout: 20000,
|
||||
});
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
|
|
@ -41,8 +44,11 @@ withEventDeliveryModes(
|
|||
});
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
// Astra DB tokens
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-token")
|
||||
|
|
|
|||
|
|
@ -28,10 +28,12 @@ withEventDeliveryModes(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "YouTube Analysis" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ test(
|
|||
await page.waitForSelector('[data-testid="input_outputChat Input"]', {
|
||||
timeout: 2000,
|
||||
});
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await zoomOut(page, 6);
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page
|
||||
.getByTestId("input_outputChat Input")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
|
||||
|
|
@ -213,7 +213,11 @@ test(
|
|||
timeout: 2000,
|
||||
});
|
||||
//----------------------------------
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
//---------------------------------- EDIT PROMPT
|
||||
await page.getByTestId("promptarea_prompt_template").first().click();
|
||||
await page
|
||||
|
|
@ -353,7 +357,11 @@ test(
|
|||
}
|
||||
await page.getByTestId("dropdown_str_model_name").click();
|
||||
await page.getByTestId("gpt-4o-1-option").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByRole("button", { name: "Playground", exact: true }).click();
|
||||
await page.waitForSelector('[data-testid="input-chat-playground"]', {
|
||||
timeout: 100000,
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
|
||||
targetPosition: { x: 0, y: 0 },
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await zoomOut(page, 5);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("text embedder");
|
||||
|
|
@ -111,7 +113,10 @@ test(
|
|||
|
||||
await updateOldComponents(page);
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page
|
||||
.getByTestId("textarea_str_template")
|
||||
|
|
@ -150,12 +155,17 @@ test(
|
|||
.nth(0)
|
||||
.fill("similarity_score");
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.mouse.wheel(0, 500);
|
||||
|
||||
await page.locator(".react-flow__pane").click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
//connection 1
|
||||
const openAiEmbeddingOutput_0 = await page
|
||||
|
|
|
|||
|
|
@ -85,11 +85,10 @@ test(
|
|||
.getByRole("heading", { name: "Vector Store RAG" })
|
||||
.first()
|
||||
.click();
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
const edges = await page.locator(".react-flow__edge-interaction").count();
|
||||
const nodes = await page.getByTestId("div-generic-node").count();
|
||||
|
|
@ -126,9 +125,7 @@ test(
|
|||
|
||||
await page.getByTestId("text_card_container").nth(i).click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
await page.waitForSelector('[data-testid="div-generic-node"]', {
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ test.skip(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
|
@ -85,6 +87,8 @@ test.skip(
|
|||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
const elementsOpenAiOutput = await page
|
||||
.getByTestId("handle-openaimodel-shownode-text-right")
|
||||
.all();
|
||||
|
|
|
|||
|
|
@ -20,11 +20,14 @@ test(
|
|||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
let outdatedComponents = await page.getByTestId("update-button").count();
|
||||
|
||||
|
|
|
|||
|
|
@ -20,11 +20,14 @@ test(
|
|||
.getByRole("heading", { name: "Vector Store RAG" })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await renameFlow(page, { flowName: randomName });
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,10 @@ test(
|
|||
await page
|
||||
.getByTestId("input_outputText Input")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'), {});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await zoomOut(page, 4);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
|
|
@ -166,10 +168,14 @@ test(
|
|||
.nth(1);
|
||||
await elementCombineTextInput1.click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTitle("fit view").click();
|
||||
|
||||
await zoomOut(page, 2);
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page
|
||||
.getByTestId("title-Combine Text")
|
||||
.first()
|
||||
|
|
|
|||
|
|
@ -21,11 +21,14 @@ test(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 2000,
|
||||
});
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("message history");
|
||||
|
|
@ -88,7 +91,10 @@ AI:
|
|||
await page.getByText("Edit Prompt", { exact: true }).click();
|
||||
await page.getByText("Check & Save").last().click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
//connection 1
|
||||
await page
|
||||
|
|
|
|||
|
|
@ -37,11 +37,14 @@ test("chat_io_teste", { tag: ["@release", "@workspace"] }, async ({ page }) => {
|
|||
targetPosition: { x: 100, y: 100 },
|
||||
});
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page
|
||||
.getByTestId("handle-chatinput-noshownode-chat message-source")
|
||||
|
|
|
|||
|
|
@ -30,8 +30,12 @@ test(
|
|||
.getByTestId("prototypesPython Function")
|
||||
.getByTestId("icon-Plus")
|
||||
.click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("div-generic-node").click();
|
||||
|
||||
await page.getByTestId("code-button-modal").click();
|
||||
|
|
|
|||
|
|
@ -698,7 +698,7 @@ test(
|
|||
await page.getByTestId("button_open_file_management").click();
|
||||
console.warn(psdFileName);
|
||||
|
||||
// Check if the PSD file has the disabled class (greyed out)
|
||||
// Check if the PNG file has the disabled class (greyed out)
|
||||
await expect(page.getByTestId(`file-item-${psdFileName}`)).toHaveClass(
|
||||
/pointer-events-none cursor-not-allowed opacity-50/,
|
||||
);
|
||||
|
|
@ -714,19 +714,6 @@ test(
|
|||
.locator("..")
|
||||
.hover();
|
||||
|
||||
await expect(
|
||||
page.getByText("Type not supported by component"),
|
||||
).toBeVisible();
|
||||
|
||||
// Try to select the PSD file (should not change its state)
|
||||
await expect(page.getByTestId(`checkbox-${psdFileName}`)).toBeDisabled();
|
||||
|
||||
// Verify the PSD file checkbox remains unchecked
|
||||
await expect(page.getByTestId(`checkbox-${psdFileName}`)).toHaveAttribute(
|
||||
"data-state",
|
||||
"unchecked",
|
||||
);
|
||||
|
||||
// Select the TXT file (should work normally)
|
||||
await page.getByTestId(`checkbox-${txtFileName}`).click();
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,10 @@ test(
|
|||
|
||||
await page.getByText("Close").last().click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.locator('//*[@id="int_int_seed"]').click();
|
||||
await page.locator('//*[@id="int_int_seed"]').fill("");
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ test("IntComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => {
|
|||
.getByTestId("openaiOpenAI")
|
||||
.first()
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await zoomOut(page, 2);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("div-generic-node").click();
|
||||
|
||||
|
|
@ -50,6 +52,8 @@ test("IntComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => {
|
|||
|
||||
await page.getByTestId("title-OpenAI").click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
|
@ -58,6 +62,7 @@ test("IntComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => {
|
|||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("edit-button-modal").last().click();
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,11 @@ test(
|
|||
);
|
||||
|
||||
await page.getByTestId("sidebar-custom-component-button").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTitle("fit view").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("title-Custom Component").first().click();
|
||||
|
||||
|
|
@ -64,6 +67,7 @@ test(
|
|||
await page.keyboard.press("Backspace");
|
||||
await page.locator("textarea").last().fill(cleanCode);
|
||||
await page.locator('//*[@id="checkAndSaveBtn"]').click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 3000,
|
||||
|
|
@ -71,6 +75,7 @@ test(
|
|||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
expect(await page.getByText("BUTTON").isVisible()).toBeTruthy();
|
||||
expect(await page.getByText("Click me").isVisible()).toBeTruthy();
|
||||
|
|
|
|||
|
|
@ -315,8 +315,11 @@ test(
|
|||
).toBeTruthy();
|
||||
|
||||
await page.getByText("Close").last().click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await zoomOut(page, 2);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("edit-button-modal").last().click();
|
||||
|
||||
await page.locator('//*[@id="showprompt1"]').click();
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("title-OpenAI").click();
|
||||
await page.getByTestId("code-button-modal").click();
|
||||
|
|
@ -63,8 +66,10 @@ test(
|
|||
await page.keyboard.press("Backspace");
|
||||
await page.locator("textarea").last().fill(newCode);
|
||||
await page.locator('//*[@id="checkAndSaveBtn"]').click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page
|
||||
.getByTestId("query_query_openai_api_base")
|
||||
|
|
|
|||
|
|
@ -26,9 +26,12 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("title-Ollama").click();
|
||||
await page.getByTestId("code-button-modal").click();
|
||||
|
|
@ -61,8 +64,10 @@ test(
|
|||
await page.keyboard.press("Backspace");
|
||||
await page.locator("textarea").last().fill(newCode);
|
||||
await page.locator('//*[@id="checkAndSaveBtn"]').click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await mutualValidation(page);
|
||||
|
||||
|
|
@ -71,8 +76,10 @@ test(
|
|||
// wait for the slider to update
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByText("Controls", { exact: true }).last().click();
|
||||
|
|
|
|||
|
|
@ -20,8 +20,11 @@ test(
|
|||
);
|
||||
|
||||
await page.getByTestId("sidebar-custom-component-button").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTitle("fit view").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("title-Custom Component").first().click();
|
||||
|
||||
|
|
@ -63,6 +66,7 @@ test(
|
|||
await page.keyboard.press("Backspace");
|
||||
await page.locator("textarea").last().fill(cleanCode);
|
||||
await page.locator('//*[@id="checkAndSaveBtn"]').click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 3000,
|
||||
|
|
@ -70,6 +74,7 @@ test(
|
|||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
// Verify that all tabs are visible
|
||||
expect(await page.getByText("Tab 1").isVisible()).toBeTruthy();
|
||||
|
|
|
|||
|
|
@ -25,14 +25,21 @@ test(
|
|||
},
|
||||
);
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="zoom_out"]', {
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("sidebar-custom-component-button").click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("div-generic-node").click();
|
||||
await page.getByTestId("code-button-modal").click();
|
||||
|
|
|
|||
|
|
@ -38,11 +38,14 @@ test(
|
|||
|
||||
await page.getByText("Close").last().click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("toggle_bool_load_hidden").click();
|
||||
expect(
|
||||
|
|
|
|||
|
|
@ -40,12 +40,14 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
expect(await page.getByTestId("save-flow-button").isEnabled()).toBeTruthy();
|
||||
|
||||
|
|
@ -106,6 +108,7 @@ test(
|
|||
console.error("Failed to hover or find add component button:", error);
|
||||
throw error;
|
||||
}
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
// Wait for fit view button
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
|
|
@ -113,6 +116,7 @@ test(
|
|||
});
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("icon-ChevronLeft").last().click();
|
||||
|
||||
|
|
@ -145,12 +149,14 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("save-flow-button").click();
|
||||
await page.getByTestId("icon-ChevronLeft").last().click();
|
||||
|
|
|
|||
|
|
@ -12,10 +12,12 @@ test(
|
|||
//add a new flow just to have the workspace available
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="icon-ChevronLeft"]', {
|
||||
timeout: 100000,
|
||||
|
|
@ -66,10 +68,12 @@ test(
|
|||
//add a new flow just to have the workspace available
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="icon-ChevronLeft"]', {
|
||||
timeout: 100000,
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ test.describe("Flow Page tests", () => {
|
|||
);
|
||||
|
||||
await page.getByTestId("sidebar-custom-component-button").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTitle("fit view").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await adjustScreenView(page);
|
||||
await page.getByTestId("generic-node-title-arrangement").click();
|
||||
|
|
|
|||
|
|
@ -20,13 +20,17 @@ test(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
state: "visible",
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("lock_unlock").click();
|
||||
await page.getByTestId("flow_name").click();
|
||||
await page.getByTestId("lock-flow-switch").click();
|
||||
await page.getByTestId("save-flow-settings").click();
|
||||
|
||||
//ensure the UI is updated
|
||||
await page.waitForTimeout(500);
|
||||
|
|
@ -41,10 +45,12 @@ test(
|
|||
});
|
||||
|
||||
await page.getByTestId("list-card").first().click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
state: "visible",
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
//ensure the UI is updated
|
||||
await page.waitForTimeout(1000);
|
||||
|
|
@ -53,10 +59,12 @@ test(
|
|||
timeout: 3000,
|
||||
});
|
||||
|
||||
await page.getByTestId("lock_unlock").click();
|
||||
await page.waitForSelector('[data-testid="icon-LockOpen"]', {
|
||||
await page.getByTestId("flow_name").click();
|
||||
await page.getByTestId("lock-flow-switch").click();
|
||||
await page.waitForSelector('[data-testid="icon-Lock"]', {
|
||||
timeout: 3000,
|
||||
});
|
||||
await page.getByTestId("save-flow-settings").click();
|
||||
|
||||
await page.getByTestId("icon-ChevronLeft").click();
|
||||
await page.waitForSelector('[data-testid="mainpage_title"]', {
|
||||
|
|
@ -64,16 +72,13 @@ test(
|
|||
});
|
||||
|
||||
await page.getByTestId("list-card").first().click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
await page.waitForSelector('[data-testid="icon-LockOpen"]', {
|
||||
timeout: 3000,
|
||||
state: "visible",
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await tryDeleteEdge(page);
|
||||
await page.locator(".react-flow__edge-path").nth(0).click();
|
||||
|
|
@ -118,7 +123,10 @@ test(
|
|||
);
|
||||
|
||||
async function tryConnectNodes(page: Page) {
|
||||
await page.getByTestId("lock_unlock").click();
|
||||
await page.getByTestId("flow_name").click();
|
||||
await page.getByTestId("lock-flow-switch").click();
|
||||
await page.getByTestId("icon-Unlock").isVisible();
|
||||
await page.getByTestId("save-flow-settings").click();
|
||||
|
||||
const numberOfTries = 5;
|
||||
let numberOfEdges = await page.locator(".react-flow__edge-path").count();
|
||||
|
|
@ -146,12 +154,18 @@ async function tryConnectNodes(page: Page) {
|
|||
expect(numberOfEdges).toBe(0);
|
||||
}
|
||||
}
|
||||
await page.getByTestId("flow_name").click();
|
||||
await page.getByTestId("lock-flow-switch").click();
|
||||
|
||||
await page.getByTestId("lock_unlock").click();
|
||||
await page.getByTestId("lock_Unlock").isVisible();
|
||||
await page.getByTestId("save-flow-settings").click();
|
||||
}
|
||||
|
||||
async function tryDeleteEdge(page: Page) {
|
||||
await page.getByTestId("lock_unlock").click();
|
||||
await page.getByTestId("flow_name").click();
|
||||
await page.getByTestId("lock-flow-switch").click();
|
||||
await page.getByTestId("icon-Lock").isVisible();
|
||||
await page.getByTestId("save-flow-settings").click();
|
||||
|
||||
let numberOfEdges = await page.locator(".react-flow__edge-path").count();
|
||||
expect(numberOfEdges).toBe(3);
|
||||
|
|
@ -168,5 +182,8 @@ async function tryDeleteEdge(page: Page) {
|
|||
expect(numberOfEdges).toBe(3);
|
||||
}
|
||||
//unlock the flow
|
||||
await page.getByTestId("lock_unlock").click();
|
||||
await page.getByTestId("flow_name").click();
|
||||
await page.getByTestId("lock-flow-switch").click();
|
||||
await page.getByTestId("lock_Unlock").isVisible();
|
||||
await page.getByTestId("save-flow-settings").click();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ test(
|
|||
timeout: 1000,
|
||||
});
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await zoomOut(page, 3);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page
|
||||
.getByTestId("dataURL")
|
||||
|
|
@ -110,10 +112,12 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
|
||||
targetPosition: { x: 940, y: 100 },
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await zoomOut(page, 2);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
// Loop Item -> Update Data
|
||||
|
||||
|
|
@ -157,7 +161,10 @@ test(
|
|||
.first()
|
||||
.click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await zoomOut(page, 3);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("div-generic-node").nth(5).click();
|
||||
|
||||
|
|
|
|||
|
|
@ -243,10 +243,12 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
|
||||
targetPosition: { x: 0, y: 0 },
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await zoomOut(page, 3);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeHidden();
|
||||
|
||||
|
|
|
|||
|
|
@ -24,10 +24,12 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
|
||||
targetPosition: { x: 0, y: 0 },
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await zoomOut(page, 3);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeHidden();
|
||||
|
||||
|
|
@ -83,8 +85,10 @@ test(
|
|||
await page.getByTestId("fetch-0-option").click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="int_int_max_length"]', {
|
||||
state: "visible",
|
||||
|
|
@ -233,10 +237,12 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
|
||||
targetPosition: { x: 0, y: 0 },
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await zoomOut(page, 3);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
try {
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
|
|
@ -406,10 +412,12 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
|
||||
targetPosition: { x: 0, y: 0 },
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await zoomOut(page, 3);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
try {
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ test(
|
|||
.getByTestId("input_outputText Input")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page
|
||||
.locator('//*[@id="react-flow-id"]')
|
||||
.hover()
|
||||
|
|
@ -33,8 +32,10 @@ test(
|
|||
await page.mouse.up();
|
||||
|
||||
await adjustScreenView(page);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await zoomOut(page, 4);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
|
||||
|
|
|
|||
|
|
@ -24,10 +24,12 @@ test(
|
|||
await page
|
||||
.getByRole("heading", { name: "Portfolio Website Code Generator" })
|
||||
.click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page, {
|
||||
skipAdjustScreenView: true,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ The future of AI is both exciting and uncertain. As the technology continues to
|
|||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page.getByTestId("add_note").click();
|
||||
|
||||
const targetElement = page.locator('//*[@id="react-flow-id"]');
|
||||
|
|
@ -47,6 +48,7 @@ The future of AI is both exciting and uncertain. As the technology continues to
|
|||
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
|
|
@ -54,6 +56,8 @@ The future of AI is both exciting and uncertain. As the technology continues to
|
|||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await zoomOut(page, 6);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("note_node").click();
|
||||
|
||||
await page.locator(".generic-node-desc-text").last().dblclick();
|
||||
|
|
@ -119,8 +123,10 @@ The future of AI is both exciting and uncertain. As the technology continues to
|
|||
await page.getByTestId("more-options-modal").click();
|
||||
|
||||
await page.getByText("Copy").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
//double click
|
||||
await targetElement.click();
|
||||
|
|
|
|||
|
|
@ -17,12 +17,19 @@ test(
|
|||
},
|
||||
);
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="zoom_out"]', {
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("sidebar-custom-component-button").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTitle("fit view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.waitForTimeout(500);
|
||||
|
|
@ -85,10 +92,11 @@ class CustomComponent(Component):
|
|||
await page.locator("textarea").fill(waitTimeoutCode);
|
||||
|
||||
await page.getByText("Check & Save").last().click();
|
||||
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
//connection 1
|
||||
const elementCustomComponentOutput = await page
|
||||
|
|
|
|||
|
|
@ -113,10 +113,12 @@ test(
|
|||
await page.getByTestId("disclosure-data").click();
|
||||
|
||||
await page.getByTestId("disclosure-agents").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await zoomOut(page, 4);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="agentsAgent"]', {
|
||||
timeout: 3000,
|
||||
|
|
@ -127,8 +129,10 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
|
||||
targetPosition: { x: 0, y: 500 },
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
// Move the Agent node a bit
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ test(
|
|||
await page.getByText("Vector Store RAG", { exact: true }).last().click();
|
||||
await page.getByText("Retriever", { exact: true }).first().isVisible();
|
||||
await page.getByText("Search Results", { exact: true }).first().isVisible();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
const focusElementsOnBoard = async ({ page }) => {
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
|
|
@ -20,6 +21,7 @@ test(
|
|||
};
|
||||
|
||||
await focusElementsOnBoard({ page });
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByText("Retriever", { exact: true }).first().isHidden();
|
||||
await page.getByTestId("icon-ChevronDown").last().isVisible();
|
||||
|
|
|
|||
|
|
@ -210,9 +210,13 @@ test(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
// Now navigate to user settings
|
||||
await page.getByTestId("user-profile-settings").click();
|
||||
await page.getByTestId("menu_settings_button").click();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
import { addCustomComponent } from "../../utils/add-custom-component";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
import { zoomOut } from "../../utils/zoom-out";
|
||||
|
||||
test(
|
||||
"user should be able to see errors on popups when raise an error",
|
||||
|
|
@ -54,9 +53,11 @@ class CustomComponent(Component):
|
|||
);
|
||||
|
||||
await addCustomComponent(page);
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,13 @@ test(
|
|||
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
await page.getByTestId("button_run_chat output").last().click();
|
||||
|
|
@ -95,8 +99,10 @@ test(
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
|
||||
targetPosition: { x: 700, y: 400 },
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
// Fill URL input
|
||||
await page
|
||||
|
|
|
|||
|
|
@ -26,8 +26,10 @@ test(
|
|||
.getByTestId("add-component-button-duckduckgo-search")
|
||||
.click();
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-input_value")
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@ test.skip(
|
|||
|
||||
await page.getByTestId("youtubeYouTube Transcripts").hover();
|
||||
await page.getByTestId("add-component-button-youtube-transcripts").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
let outdatedComponents = await page.getByTestId("update-button").count();
|
||||
|
||||
|
|
@ -26,8 +28,10 @@ test.skip(
|
|||
await page
|
||||
.getByTestId("textarea_str_url")
|
||||
.fill("https://www.youtube.com/watch?v=VqhCQZaH4Vs");
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("button_run_youtube transcripts").click();
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ test(
|
|||
await awaitBootstrapTest(page);
|
||||
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("prompt");
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue