docs: add comprehensive cursor guidelines and rules for development practices (#8401)

* Update cursor rules with specific backend, frontend, docs

* Update image example

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* update image example

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Refactor frontend development guidelines: streamline sections, remove outdated icon development instructions, and update checklist for clarity and consistency.

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
Gabriel Luiz Freitas Almeida 2025-06-12 09:51:18 -03:00 committed by GitHub
commit ab1ed8ea01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 1670 additions and 17 deletions

View file

@ -0,0 +1,268 @@
---
description: "Guidelines for backend development in Langflow, focusing on Python components, FastAPI services, and backend testing."
globs:
- "src/backend/**/*.py"
- "tests/**/*.py"
- "Makefile"
- "pyproject.toml"
- "uv.lock"
alwaysApply: false
---
# Backend Development Guidelines
## Purpose
Guidelines for backend development in Langflow, focusing on Python components, FastAPI services, and backend testing.
---
## 1. Backend Environment Setup
### Prerequisites
- **Python Package Manager:** `uv` (>=0.4) for dependency management
- **Database:** SQLite for development, PostgreSQL for production
- **Development Tools:** `make` for build coordination
### Backend Service
```bash
make backend # Start FastAPI backend on port 7860
```
- Auto-reloads on file changes
- Health check: http://localhost:7860/health
- Backend components: `src/backend/base/langflow/`
---
## 2. Component Development
### Component Structure
```
src/backend/base/langflow/components/
├── agents/ # Agent components
├── data/ # Data processing components
├── embeddings/ # Embedding components
├── input_output/ # Input/output components
├── models/ # Language model components
├── processing/ # Text processing components
├── prompts/ # Prompt components
├── tools/ # Tool components
└── vectorstores/ # Vector store components
```
### Adding New Components
1. **Location:** Add to appropriate subdirectory under `src/backend/base/langflow/components/`
2. **Import:** Update `__init__.py` with alphabetical imports:
```python
from .my_component import MyComponent
__all__ = [
"ExistingComponent",
"MyComponent", # Add alphabetically
]
```
3. **Auto-restart:** Backend auto-restarts on save
4. **Browser refresh:** Refresh browser to see component changes
### Component Testing
- **Unit Tests:** `src/backend/tests/unit/components/`
- **Test Structure:** Mirror component directory structure
- **Test Base Classes:** Use `ComponentTestBaseWithClient` or `ComponentTestBaseWithoutClient`
- **Version Testing:** Provide `file_names_mapping` for backward compatibility
### Development Tips
- **Fast iteration:** Edit component in UI first, then save to source
- **Component updates:** Old components show "Updates Available" after backend restart
- **Testing:** Create comprehensive unit tests for all new components
---
## 3. Backend Code Quality
### Formatting (CRITICAL)
```bash
make format_backend # Format Python code
```
**Important:** Run `make format_backend` _early and often_ (ideally before running linting or committing changes). It auto-corrects the majority of style issues, preventing lengthy manual fixes when lint errors surface later.
### Linting
```bash
make lint # Run linting checks
```
### Testing
```bash
make unit_tests # Run backend unit tests
```
### Pre-commit Workflow
1. **Run `make format_backend`** (FIRST - saves time on lint fixes)
2. Run `make lint`
3. Run `make unit_tests`
4. Commit changes
---
## 4. FastAPI Development
### API Structure
```
src/backend/base/langflow/api/
├── v1/ # API version 1
│ ├── chat.py # Chat endpoints
│ ├── flows.py # Flow management
│ ├── users.py # User management
│ └── ...
└── v2/ # API version 2 (future)
```
### Testing APIs
- Use `client` fixture from `conftest.py`
- Test with `logged_in_headers` for authenticated endpoints
- Example:
```python
async def test_flows_endpoint(client, logged_in_headers):
response = await client.post(
"api/v1/flows/",
json=flow_data,
headers=logged_in_headers
)
assert response.status_code == 201
```
---
## 5. Database Development
### Models Location
```
src/backend/base/langflow/services/database/models/
├── api_key/ # API key models
├── flow/ # Flow models
├── folder/ # Folder models
├── user/ # User models
└── ...
```
### Database Testing
- Use in-memory SQLite for tests
- Database tests may fail in batch runs - run individually if needed:
```bash
uv run pytest src/backend/tests/unit/test_database.py
```
---
## 6. Async Development Patterns
### Component Async Methods
```python
async def run(self) -> MessageType:
"""Main component execution method."""
# Use await for async operations
result = await self.async_operation()
return result
async def message_response(self) -> Message:
"""Return a Message object for chat components."""
return Message(
text=self.input_value,
sender=self.sender,
session_id=self.session_id,
)
```
### Background Tasks
```python
import asyncio
async def process_in_background(self):
"""Process items without blocking."""
# Use asyncio.create_task for background work
task = asyncio.create_task(self.heavy_operation())
# Ensure proper cleanup
try:
result = await task
return result
except asyncio.CancelledError:
# Handle cancellation gracefully
await self.cleanup()
raise
```
### Queue Operations
```python
async def queue_processing(self):
"""Non-blocking queue operations."""
queue = asyncio.Queue()
# Non-blocking put
queue.put_nowait(data)
# Timeout-controlled get
try:
result = await asyncio.wait_for(queue.get(), timeout=5.0)
return result
except asyncio.TimeoutError:
# Handle timeout appropriately
raise ComponentError("Processing timeout")
```
---
## 7. Component Integration Testing
### Flow Testing
```python
from tests.unit.build_utils import create_flow, build_flow, get_build_events
async def test_component_in_flow(client, json_flow, logged_in_headers):
"""Test component within a complete flow."""
flow_id = await create_flow(client, json_flow, logged_in_headers)
build_response = await build_flow(client, flow_id, logged_in_headers)
# Validate flow execution
job_id = build_response["job_id"]
events_response = await get_build_events(client, job_id, logged_in_headers)
assert events_response.status_code == 200
```
### External API Testing
```python
@pytest.mark.api_key_required
@pytest.mark.no_blockbuster
async def test_with_real_api(self):
"""Test component with external service."""
api_key = os.getenv("OPENAI_API_KEY")
component = MyComponent(api_key=api_key, model="gpt-4o")
response = await component.run()
assert response is not None
```
---
## 8. Known Backend Issues
### Testing Quirks
- `test_database.py` may fail in batch runs but pass individually
- Use `@pytest.mark.no_blockbuster` to skip blockbuster plugin when needed
- Context variables may not propagate correctly in `asyncio.to_thread` - test both patterns
### File Changes
- Starter project files auto-format after `langflow run`
- These formatting changes can be committed or ignored
---
## Backend Development Checklist
- [ ] Component added to appropriate subdirectory
- [ ] `__init__.py` updated with alphabetical imports
- [ ] Code formatted with `make format_backend` (FIRST)
- [ ] Linting passed with `make lint`
- [ ] Unit tests created and passing with `make unit_tests`
- [ ] Component tested in UI with backend restart + browser refresh
- [ ] Version mapping provided for backward compatibility
- [ ] Async patterns implemented correctly with proper cleanup
- [ ] External API calls use appropriate pytest markers

View file

@ -1,8 +1,12 @@
---
description: Rules and checklist for creating a basic Langflow Component
globs:
description: "Guide for creating a basic Langflow component, including requirements gathering, component structure, and implementation best practices."
globs:
- "src/backend/**/components/**/*.py"
- "src/backend/base/langflow/components/**/*.py"
alwaysApply: false
---
# Rule: How to Create a Basic Langflow Component
## Purpose

View file

@ -0,0 +1,440 @@
---
description: "Guidelines for developing and maintaining Langflow documentation using Docusaurus, including content structure, style, and deployment processes."
globs:
- "docs/**/*.{md,mdx}"
- "docs/**/*.{js,jsx,ts,tsx}"
- "docs/package*.json"
- "docs/docusaurus.config.js"
- "docs/sidebars.js"
- "docs/static/**/*"
alwaysApply: false
---
# Documentation Development Guidelines
## Purpose
Guidelines for developing and maintaining Langflow documentation using Docusaurus, including content structure, style, and deployment processes.
---
## 1. Documentation Environment Setup
### Prerequisites
- **Node.js:** v22.12 LTS for runtime
- **Package Manager:** Yarn for dependency management
- **Documentation Framework:** Docusaurus v3
### Documentation Service
```bash
cd docs
yarn install # Install dependencies
yarn start # Start dev server (usually port 3001)
```
- Auto-reloads on documentation changes
- Access at: http://localhost:3001/
- Documentation source: `docs/`
---
## 2. Documentation Structure
### Directory Layout
```
docs/
├── docs/ # Main documentation content
│ ├── getting-started/ # Getting started guides
│ ├── components/ # Component documentation
│ ├── integrations/ # Third-party integrations
│ ├── administration/ # Admin and deployment guides
│ ├── contributing/ # Contribution guidelines
│ └── api-reference/ # API documentation
├── blog/ # Blog posts and announcements
├── src/ # Custom React components
├── static/ # Static assets (images, etc.)
├── sidebars.js # Sidebar configuration
├── docusaurus.config.js # Main configuration
└── package.json # Dependencies
```
### Content Types
- **Guides:** Step-by-step tutorials (`docs/getting-started/`)
- **Reference:** API and component reference (`docs/api-reference/`)
- **How-to:** Problem-solving articles (`docs/components/`)
- **Concepts:** Explanatory articles about Langflow concepts
- **Blog:** Release notes, announcements (`blog/`)
---
## 3. Writing Documentation
### Markdown Conventions
```markdown
---
title: Page Title
description: Brief description for SEO
sidebar_position: 1
---
# Page Title
Brief introduction paragraph.
## Section Header
Content with proper formatting.
### Subsection
More detailed content.
:::tip
Use admonitions for important information.
:::
:::warning
Use warnings for potential issues.
:::
:::danger
Use danger for critical warnings.
:::
```
### Code Blocks
````markdown
```python title="component_example.py"
from langflow.components.base import Component
class MyComponent(Component):
display_name = "My Component"
description = "Example component"
def run(self):
return "Hello, World!"
```
````
### Images and Assets
```markdown
<!-- Images go in static/img/ -->
![Component Overview](/img/components/overview.png)
<!-- Use descriptive alt text -->
![Langflow interface showing the flow editor with nodes and connections](/img/flow-editor.png)
```
---
## 4. Component Documentation
### Component Page Template
```markdown
---
title: Component Name
description: Brief description of what the component does
sidebar_position: 1
---
# Component Name
Brief overview of the component's purpose.
## Overview
What this component does and when to use it.
## Configuration
### Inputs
| Input | Type | Required | Description |
|-------|------|----------|-------------|
| `input_text` | String | Yes | The text to process |
| `model_name` | String | No | Model to use (default: gpt-3.5-turbo) |
### Outputs
| Output | Type | Description |
|--------|------|-------------|
| `result` | Message | Processed result |
## Usage Example
```python
# Example of using the component
component = MyComponent(
input_text="Hello, world!",
model_name="gpt-4"
)
result = component.run()
```
## Common Issues
### Issue: Component not loading
**Solution:** Check that all required inputs are provided.
### Issue: API key errors
**Solution:** Ensure your API key is properly configured.
```
### API Documentation
```markdown
---
title: API Endpoint
description: REST API endpoint documentation
---
# API Endpoint Name
## Endpoint
`POST /api/v1/endpoint`
## Request
### Headers
```json
{
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
```
### Body
```json
{
"parameter": "value",
"optional_param": "optional_value"
}
```
## Response
### Success (200)
```json
{
"success": true,
"data": {
"result": "success"
}
}
```
### Error (400)
```json
{
"success": false,
"error": "Error message"
}
```
## Example
```bash
curl -X POST http://localhost:7860/api/v1/endpoint \
-H "Authorization: Bearer your-token" \
-H "Content-Type: application/json" \
-d '{"parameter": "value"}'
```
```
---
## 5. Blog Posts and Announcements
### Blog Post Template
```markdown
---
title: "Release: Langflow v1.1.0"
description: "New features and improvements in Langflow v1.1.0"
authors: [author-name]
date: 2024-01-15
tags: [release, features]
---
# Release: Langflow v1.1.0
Brief introduction to the release.
## New Features
### Feature 1
Description of the feature and how to use it.
### Feature 2
Another feature description.
## Improvements
- List of improvements
- Bug fixes
- Performance enhancements
## Breaking Changes
:::warning
List any breaking changes that require user action.
:::
## Migration Guide
Steps to migrate from previous versions.
```
### Announcement Posts
```markdown
---
title: "Announcement: New Integration"
description: "Langflow now supports integration with XYZ service"
authors: [author-name]
date: 2024-01-15
tags: [announcement, integration]
---
Brief announcement content with clear call-to-action.
```
---
## 6. Configuration
### Sidebar Configuration (`sidebars.js`)
```javascript
module.exports = {
docs: [
'introduction',
{
type: 'category',
label: 'Getting Started',
items: [
'getting-started/installation',
'getting-started/quickstart',
'getting-started/first-flow',
],
},
{
type: 'category',
label: 'Components',
items: [
'components/overview',
'components/inputs',
'components/outputs',
'components/processing',
],
},
],
};
```
### Site Configuration (`docusaurus.config.js`)
```javascript
module.exports = {
title: 'Langflow Documentation',
tagline: 'Build AI flows visually',
url: 'https://docs.langflow.org',
baseUrl: '/',
themeConfig: {
navbar: {
title: 'Langflow',
logo: {
alt: 'Langflow Logo',
src: 'img/logo.svg',
},
items: [
{
type: 'doc',
docId: 'introduction',
position: 'left',
label: 'Docs',
},
{
to: '/blog',
label: 'Blog',
position: 'left'
},
],
},
},
};
```
---
## 7. Documentation Testing
### Link Checking
```bash
# Check for broken internal links
yarn build
yarn serve
# Manual testing or use link checker tools
```
### Content Review
- **Accuracy:** Verify all code examples work
- **Completeness:** Ensure all features are documented
- **Clarity:** Review for clear, concise language
- **Navigation:** Test sidebar and cross-references
### Screenshots
- Keep screenshots up-to-date with current UI
- Use consistent browser/OS for screenshots
- Highlight relevant UI elements
- Use descriptive file names
---
## 8. Style Guide
### Writing Style
- **Tone:** Professional but approachable
- **Voice:** Second person ("you") for instructions
- **Tense:** Present tense for current features
- **Length:** Keep paragraphs short and scannable
### Formatting
- **Headers:** Use sentence case
- **Code:** Inline code with `backticks`
- **Emphasis:** Use **bold** for UI elements, *italic* for emphasis
- **Lists:** Use parallel structure
### Terminology
- **Langflow:** Always capitalize
- **Component:** Capitalize when referring to Langflow components
- **Flow:** Capitalize when referring to Langflow flows
- **API:** Always uppercase
- **JSON:** Always uppercase
---
## 9. Deployment
### Local Testing
```bash
yarn build # Build static site
yarn serve # Serve built site locally
```
### Production Deployment
- Documentation is automatically deployed on commit to main branch
- Build artifacts go to `build/` directory
- Static site is served via CDN
---
## Documentation Development Checklist
- [ ] Documentation service running with `yarn start`
- [ ] Content follows markdown conventions
- [ ] Code examples are tested and working
- [ ] Images have descriptive alt text
- [ ] Internal links are functional
- [ ] Sidebar navigation is updated
- [ ] Content follows style guide
- [ ] Screenshots are current and properly formatted
- [ ] Cross-references between related topics
- [ ] Build succeeds with `yarn build`

View file

@ -0,0 +1,374 @@
---
description: "Guidelines for frontend development in Langflow, focusing on React/TypeScript UI components, build processes, and frontend testing."
globs:
- "src/frontend/**/*.{ts,tsx,js,jsx}"
- "src/frontend/**/*.{css,scss,json}"
- "src/frontend/package*.json"
- "src/frontend/vite.config.*"
- "src/frontend/tailwind.config.*"
- "src/frontend/tsconfig.json"
alwaysApply: false
---
## 1. Frontend Environment Setup
### Prerequisites
- **Node.js:** v22.12 LTS for JavaScript runtime
- **Package Manager:** npm (v10.9) for dependency management
- **Development Tools:** Vite for build tooling
### Frontend Service
```bash
make frontend # Start Vite dev server on port 3000
```
- Hot-reload enabled for UI changes
- Access at: http://localhost:3000/
- Frontend source: `src/frontend/`
---
## 2. Frontend Structure
### Directory Layout
```
src/frontend/src/
├── components/ # Reusable UI components
├── pages/ # Page-level components
├── icons/ # Component icons and lazy loading
├── stores/ # State management (Zustand)
├── types/ # TypeScript type definitions
├── utils/ # Utility functions
├── hooks/ # Custom React hooks
├── services/ # API service functions
└── assets/ # Static assets
```
### Key Technologies
- **React 18** with TypeScript
- **Vite** for build tooling and dev server
- **Tailwind CSS** for styling
- **Zustand** for state management
- **React Flow** for flow graph visualization
- **Lucide React** for icons
---
## 3. Frontend Code Quality
### Formatting
```bash
make format_frontend # Format TypeScript/JavaScript code
```
### Linting
```bash
make lint # Run ESLint and TypeScript checks
```
### Testing
```bash
make tests_frontend # Run frontend tests (requires additional setup)
```
### Pre-commit Workflow
1. Run `make format_frontend`
2. Run `make lint`
3. Test changes in browser
4. Commit changes
---
## 4. State Management
### Zustand Stores
```typescript
// stores/myStore.ts
import { create } from 'zustand';
interface MyState {
value: string;
setValue: (value: string) => void;
}
export const useMyStore = create<MyState>((set) => ({
value: '',
setValue: (value) => set({ value }),
}));
```
### Using Stores in Components
```typescript
// components/MyComponent.tsx
import { useMyStore } from '@/stores/myStore';
export function MyComponent() {
const { value, setValue } = useMyStore();
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
```
---
## 5. API Integration
### Service Functions
```typescript
// services/api.ts
import { api } from '@/controllers/API';
export async function createFlow(flowData: FlowData) {
const response = await api.post('/flows/', flowData);
return response.data;
}
export async function getFlows() {
const response = await api.get('/flows/');
return response.data;
}
```
### Error Handling
```typescript
// hooks/useApi.ts
import { useState, useCallback } from 'react';
export function useApi<T>(apiFunction: (...args: any[]) => Promise<T>) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const execute = useCallback(async (...args: any[]) => {
try {
setLoading(true);
setError(null);
const result = await apiFunction(...args);
return result;
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
throw err;
} finally {
setLoading(false);
}
}, [apiFunction]);
return { execute, loading, error };
}
```
---
## 6. React Flow Integration
### Flow Graph Components
```typescript
// components/FlowGraph.tsx
import ReactFlow, {
Node,
Edge,
Controls,
Background
} from 'reactflow';
interface FlowGraphProps {
nodes: Node[];
edges: Edge[];
onNodesChange: (changes: NodeChange[]) => void;
onEdgesChange: (changes: EdgeChange[]) => void;
}
export function FlowGraph({
nodes,
edges,
onNodesChange,
onEdgesChange
}: FlowGraphProps) {
return (
<div className="w-full h-full">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
>
<Controls />
<Background />
</ReactFlow>
</div>
);
}
```
### Custom Node Types
```typescript
// components/nodes/ComponentNode.tsx
import { memo } from 'react';
import { Handle, Position } from 'reactflow';
interface ComponentNodeProps {
data: {
label: string;
icon?: string;
};
}
export const ComponentNode = memo(({ data }: ComponentNodeProps) => {
return (
<div className="px-4 py-2 shadow-md rounded-md bg-white border">
<Handle type="target" position={Position.Top} />
<div className="flex items-center">
{data.icon && (
<img src={data.icon} alt="" className="w-4 h-4 mr-2" />
)}
<span className="text-sm">{data.label}</span>
</div>
<Handle type="source" position={Position.Bottom} />
</div>
);
});
```
---
## 7. Styling with Tailwind
### Component Styling
```typescript
// components/Button.tsx
import { cn } from '@/utils/cn';
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
onClick?: () => void;
}
export function Button({
variant = 'primary',
size = 'md',
children,
onClick
}: ButtonProps) {
return (
<button
className={cn(
'rounded-md font-medium transition-colors',
{
'bg-blue-600 hover:bg-blue-700 text-white': variant === 'primary',
'bg-gray-200 hover:bg-gray-300 text-gray-900': variant === 'secondary',
'px-2 py-1 text-sm': size === 'sm',
'px-4 py-2 text-base': size === 'md',
'px-6 py-3 text-lg': size === 'lg',
}
)}
onClick={onClick}
>
{children}
</button>
);
}
```
### Dark Mode Support
```typescript
// hooks/useDarkMode.ts
import { useDarkStore } from '@/stores/darkStore';
export function useDarkMode() {
const { dark, setDark } = useDarkStore();
const toggle = () => setDark(!dark);
return { isDark: dark, toggle };
}
```
---
## 8. Frontend Testing
### Component Testing
```typescript
// __tests__/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '@/components/Button';
describe('Button', () => {
it('renders with correct text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
```
### Integration Testing
```typescript
// __tests__/FlowEditor.test.tsx
import { render, screen } from '@testing-library/react';
import { FlowEditor } from '@/pages/FlowEditor';
describe('FlowEditor', () => {
it('loads flow data correctly', async () => {
render(<FlowEditor flowId="test-flow-id" />);
// Wait for flow to load
await screen.findByText('Flow loaded');
expect(screen.getByTestId('flow-canvas')).toBeInTheDocument();
});
});
```
---
## 9. Build and Deployment
### Development Build
```bash
make build_frontend # Build frontend static files
```
### Production Build
```bash
cd src/frontend
npm run build # Creates dist/ directory
```
### Build Integration
- Frontend builds to `src/frontend/dist/`
- Backend serves static files from this directory in production
- `make init` includes frontend build step
---
## Frontend Development Checklist
- [ ] Frontend service running with `make frontend`
- [ ] Changes hot-reload correctly in browser
- [ ] State management uses Zustand stores
- [ ] API calls use proper error handling
- [ ] Components styled with Tailwind CSS
- [ ] Dark mode support implemented where needed
- [ ] Code formatted with `make format_frontend`
- [ ] Linting passed with `make lint`
- [ ] Changes tested in both light and dark mode
- [ ] Components styled with Tailwind CSS
- [ ] Dark mode support implemented where needed
- [ ] Code formatted with `make format_frontend`
- [ ] Changes tested in both light and dark mode

View file

@ -1,8 +1,14 @@
---
description: Rules and checklist for adding and using langflow component icons.
globs:
description: "Rules for creating and implementing component icons in Langflow, covering both backend Python component icon attributes and frontend React icon implementation."
globs:
- "src/frontend/src/icons/**/*"
- "src/frontend/src/icons/lazyIconImports.ts"
- "src/backend/**/*component*.py"
- "src/backend/**/components/**/*.py"
alwaysApply: false
---
# Component Icon Rules
## Purpose
@ -13,12 +19,12 @@ To ensure consistent, clear, and functional icon usage for components, covering
## 1. Backend (Python) — Setting the Icon Name
- **Where:** In your component class (e.g., in `src/backend/base/langflow/components/vectorstores/astradb.py`)
- **How:**
- **How:**
Set the `icon` attribute to a string matching the icon you want to use.
```python
icon = "AstraDB"
```
- **Tip:**
- **Tip:**
The string must match the frontend icon mapping exactly (case-sensitive).
---
@ -27,9 +33,9 @@ To ensure consistent, clear, and functional icon usage for components, covering
### a. Create the Icon Component
- **Where:**
- **Where:**
In a new directory for your icon, e.g., `src/frontend/src/icons/AstraDB/`.
- **How:**
- **How:**
- Add your SVG as a React component, e.g., `AstraSVG` in `AstraDB.jsx`.
```jsx
const AstraSVG = (props) => (
@ -74,28 +80,28 @@ To ensure consistent, clear, and functional icon usage for components, covering
### b. Add to Lazy Icon Imports
- **Where:**
- **Where:**
In `src/frontend/src/icons/lazyIconImports.ts`
- **How:**
- **How:**
Add an entry to the `lazyIconsMapping` object:
```ts
AstraDB: () =>
import("@/icons/AstraDB").then((mod) => ({ default: mod.AstraDBIcon })),
```
- **Tip:**
- **Tip:**
The key (`AstraDB`) must match the string used in the backend.
---
## 3. Best Practices
- **Naming:**
- **Naming:**
Use clear, recognizable names (e.g., `"AstraDB"`, `"Postgres"`, `"OpenAI"`).
- **Consistency:**
- **Consistency:**
Always use the same icon name for the same service across backend and frontend.
- **Missing Icon:**
- **Missing Icon:**
If no icon exists, use a [lucide icon](https://lucide.dev/icons)
- **Light/Dark Mode:**
- **Light/Dark Mode:**
Always support both light and dark mode for custom icons by using the `isdark` prop in your SVG.
---
@ -114,11 +120,11 @@ To ensure consistent, clear, and functional icon usage for components, covering
---
**Example for AstraDB:**
- Backend:
- Backend:
```python
icon = "AstraDB"
```
- Frontend:
- Frontend:
- `src/icons/AstraDB/AstraDB.jsx` (SVG as React component, uses `isdark` prop)
- `src/icons/AstraDB/index.tsx` (exports `AstraDBIcon` and passes `isdark`)
- Add to `lazyIconImports.ts`:

561
.cursor/rules/testing.mdc Normal file
View file

@ -0,0 +1,561 @@
---
description: "Guidelines for testing Langflow components and backend code, with emphasis on async patterns and robust, well-documented testing practices."
globs:
- "src/backend/tests/**/*.py"
- "src/frontend/**/*.test.{ts,tsx,js,jsx}"
- "src/frontend/**/*.spec.{ts,tsx,js,jsx}"
- "tests/**/*.py"
- "conftest.py"
- "pytest.ini"
alwaysApply: false
---
# Testing Guidelines for Langflow
## Purpose
Guidelines for testing Langflow components and backend code, with emphasis on async patterns and robust, well-documented testing practices.
---
## 1. Testing Structure
### Backend Tests Location
- **Unit Tests:** `src/backend/tests/`
- **Component Tests:** `src/backend/tests/unit/components/` (organized by component subdirectory)
- **Integration Tests:** Available via `make integration_tests` (requires additional setup)
### Test File Naming
- Use same filename as component with appropriate test prefix/suffix
- Example: `my_component.py` → `test_my_component.py`
---
## 2. Built-in Fixtures & Base Classes
### `client` Fixture (FastAPI Test Client)
- Defined in `src/backend/tests/conftest.py`
- Provides an **async** `httpx.AsyncClient` connected to the full application via `ASGITransport` + `LifespanManager`.
- Use it for API tests:
```python
async def test_login_endpoint(client):
response = await client.post("api/v1/login", data={"username": "foo", "password": "bar"})
assert response.status_code == 200
```
- Automatically configured with an **in-memory SQLite database** and mocked environment variables. No additional setup needed in individual tests.
- Skip client creation by marking the test with `@pytest.mark.noclient`.
### `ComponentTestBase` Family
Located in `src/backend/tests/base.py`.
| Base Class | Creates `client`? | Typical Use | Notes |
|------------|------------------|-------------|-------|
| `ComponentTestBase` | No | Core logic for component version testing | Requires you to provide fixtures described below. |
| `ComponentTestBaseWithClient` | Yes (`@pytest.mark.usefixtures("client")`) | Components that need API access during `run()` | Inherit when the component interacts with backend services. |
| `ComponentTestBaseWithoutClient` | No | Pure-logic components with no API calls | Lightweight alternative. |
Required fixtures for subclasses:
1. `component_class` → the component **class** under test.
2. `default_kwargs` → dict of kwargs to instantiate the component (can be empty).
3. `file_names_mapping` → list of `VersionComponentMapping` dicts mapping historical **Langflow versions** to module/file names.
Example skeleton:
```python
from tests.base import ComponentTestBaseWithClient, VersionComponentMapping, DID_NOT_EXIST
from langflow.components.my_namespace import MyComponent
class TestMyComponent(ComponentTestBaseWithClient):
@pytest.fixture
def component_class(self):
return MyComponent
@pytest.fixture
def default_kwargs(self):
return {"foo": "bar"}
@pytest.fixture
def file_names_mapping(self):
return [
VersionComponentMapping(version="1.1.1", module="my_module", file_name="my_component.py"),
# Older versions can be mapped or DID_NOT_EXIST
VersionComponentMapping(version="1.0.19", module="my_module", file_name=DID_NOT_EXIST),
]
```
`ComponentTestBase` automatically provides:
- `test_latest_version` → Instantiates component via `component_class` and asserts `run()` doesn't return `None`.
- `test_all_versions_have_a_file_name_defined` → Ensures mapping completeness vs `SUPPORTED_VERSIONS` constant (`src/backend/tests/constants.py`).
- `test_component_versions` (parametrised) → Builds component from source for each supported version and asserts successful execution.
When adding a new component test, **inherit from the correct base class and provide the three fixtures**. This greatly reduces boilerplate and enforces version compatibility.
---
## 3. Component Testing Requirements
### Minimum Testing Requirements
- **Unit Tests:** Create comprehensive unit tests for all new components
- **Manual Test Documentation:** If unit tests are incomplete, create a Markdown file with manual testing steps
- Location: Same directory as unit tests
- Filename: Same as component but with `.md` extension
- Content: Detailed manual testing steps and expected outcomes
### Testing Best Practices
- Test both sync and async code paths
- Mock external dependencies appropriately
- Test error handling and edge cases
- Validate input/output behavior
- Test component initialization and configuration
---
## 4. Async Testing Patterns
### Async Component Testing
```python
import pytest
import asyncio
@pytest.mark.asyncio
async def test_async_component():
# Test async component methods
result = await component.async_method()
assert result is not None
```
### Testing Background Tasks
```python
@pytest.mark.asyncio
async def test_background_task_completion():
# Ensure background tasks complete properly
task = asyncio.create_task(component.background_operation())
result = await asyncio.wait_for(task, timeout=5.0)
assert result.success
```
### Testing Queue Operations
```python
@pytest.mark.asyncio
async def test_queue_operations():
# Test queue put/get operations without blocking
queue = asyncio.Queue()
# Non-blocking put
queue.put_nowait(test_data)
# Verify queue processing
result = await asyncio.wait_for(queue.get(), timeout=1.0)
assert result == test_data
```
---
## 5. Special Testing Considerations
### Blockbuster Plugin
- Use `no_blockbuster` pytest marker to skip blockbuster plugin in tests
- Example: `@pytest.mark.no_blockbuster`
### Database Tests
- `test_database.py` may fail in batch runs but pass individually
- Run problematic tests sequentially: `uv run pytest src/backend/tests/unit/test_database.py`
- Consider this behavior when writing database-related tests
### Context Variables in Async Tests
- Be aware of ContextVar propagation in async tests
- Test both direct event loop execution and `asyncio.to_thread` scenarios
- Ensure proper context isolation between test cases
---
## 6. Test Execution
### Running Tests
```bash
make unit_tests # Run all backend unit tests
make integration_tests # Run integration tests (requires additional setup)
make coverage # Run tests with coverage (requires additional setup)
make tests_frontend # Run frontend tests (requires additional setup)
```
### Individual Test Execution
```bash
# Run specific test file
uv run pytest src/backend/tests/unit/test_specific_component.py
# Run specific test method
uv run pytest src/backend/tests/unit/test_component.py::test_specific_method
# Run with verbose output
uv run pytest -v src/backend/tests/unit/
```
---
## 7. Robust Testing Patterns
### Error Handling Tests
```python
def test_component_error_handling():
"""Test that component handles errors gracefully."""
with pytest.raises(ExpectedError):
component.method_that_should_fail(invalid_input)
# Test error message quality
try:
component.risky_operation()
except ComponentError as e:
assert "helpful error message" in str(e)
```
### Resource Cleanup Tests
```python
@pytest.fixture
async def component_with_cleanup():
"""Fixture ensuring proper resource cleanup."""
component = MyComponent()
await component.initialize()
try:
yield component
finally:
await component.cleanup()
```
### Timeout and Performance Tests
```python
@pytest.mark.asyncio
async def test_operation_timeout():
"""Test that operations respect timeout constraints."""
start_time = time.time()
with pytest.raises(asyncio.TimeoutError):
await asyncio.wait_for(
component.long_running_operation(),
timeout=1.0
)
elapsed = time.time() - start_time
assert elapsed < 1.5 # Allow some tolerance
```
---
## 8. Documentation in Tests
### Test Documentation Standards
- Each test should have a clear docstring explaining its purpose
- Complex test setups should be commented
- Mock usage should be documented
- Expected behaviors should be explicitly stated
### Example Well-Documented Test
```python
async def test_component_background_processing():
"""
Test that component processes items in background without blocking.
This test verifies:
1. Items are added to processing queue immediately
2. Background processing completes successfully
3. No deadlocks occur during shutdown
4. All tasks are properly cleaned up
"""
# Setup component with background processing
component = BackgroundComponent()
await component.start()
try:
# Add items for processing
for i in range(10):
await component.add_item(f"item_{i}")
# Verify items are queued (non-blocking)
assert component.queue_size() == 10
# Wait for processing to complete
await component.wait_for_completion(timeout=5.0)
# Verify all items processed
assert component.processed_count() == 10
finally:
# Ensure clean shutdown
await component.stop()
assert component.is_stopped()
```
---
## Testing Checklist
- [ ] Unit tests created for all new components
- [ ] Async patterns tested appropriately
- [ ] Error handling and edge cases covered
- [ ] Manual testing documentation created (if unit tests incomplete)
- [ ] Background tasks and queues tested for proper cleanup
- [ ] Database tests considered for sequential execution if needed
- [ ] Appropriate pytest markers used (`no_blockbuster`, etc.)
- [ ] Tests are well-documented with clear docstrings
- [ ] Resource cleanup properly handled in fixtures
- [ ] Timeout and performance constraints validated
---
## 9. Langflow-Specific Testing Patterns
### Message Testing
Test Langflow's `Message` objects and chat functionality:
```python
from langflow.schema.message import Message
from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER
async def test_message_response(self, component_class, default_kwargs):
"""Test Message object creation and properties."""
component = component_class(**default_kwargs)
message = await component.message_response()
assert isinstance(message, Message)
assert message.text == default_kwargs["input_value"]
assert message.sender == default_kwargs["sender"]
assert message.sender_name == default_kwargs["sender_name"]
assert message.session_id == default_kwargs["session_id"]
assert message.files == default_kwargs["files"]
# Test message properties structure
assert message.properties.model_dump() == {
"background_color": default_kwargs["background_color"],
"text_color": default_kwargs["text_color"],
"icon": default_kwargs["chat_icon"],
# ... other expected properties
}
```
### Flow Testing with JSON Data
Use predefined JSON flows and utility functions:
```python
from tests.unit.build_utils import create_flow, build_flow, get_build_events, consume_and_assert_stream
async def test_flow_execution(client, json_memory_chatbot_no_llm, logged_in_headers):
"""Test flow creation, building, and execution."""
# Create flow from JSON data
flow_id = await create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
# Start the build process
build_response = await build_flow(client, flow_id, logged_in_headers)
job_id = build_response["job_id"]
# Get and validate event stream
events_response = await get_build_events(client, job_id, logged_in_headers)
assert events_response.status_code == codes.OK
# Process events and validate structure
await consume_and_assert_stream(events_response, job_id)
```
### Component Testing with Real Services
Test components that need external APIs with proper markers:
```python
@pytest.mark.api_key_required
@pytest.mark.no_blockbuster
async def test_component_with_external_api(self):
"""Test component with external API integration."""
api_key = os.getenv("OPENAI_API_KEY")
component = MyAPIComponent(
api_key=api_key,
model_name="gpt-4o",
input_value="test input",
session_id=str(uuid4()),
)
response = await component.message_response()
assert response.data.get("text") is not None
```
### Mocking Language Models
Use `MockLanguageModel` for testing without external APIs:
```python
from tests.unit.mock_language_model import MockLanguageModel
@pytest.fixture
def default_kwargs(self):
return {
"agent_llm": MockLanguageModel(),
"input_value": "test message",
"session_id": str(uuid4()),
# ... other component-specific kwargs
}
```
### File Handling in Tests
Use `anyio` and `aiofiles` for async file operations:
```python
import aiofiles
import anyio
async def test_file_operations(self, component_class, tmp_path):
"""Test component file handling."""
# Create temporary test file
test_file = anyio.Path(tmp_path) / "test.txt"
await test_file.write_text("Test content", encoding="utf-8")
# Test with file input
component = component_class(files=[str(test_file)])
result = await component.run()
# Verify file was processed
assert len(result.files) == 1
assert await test_file.exists()
```
### API Endpoint Testing
Test Langflow's REST API endpoints:
```python
async def test_flows_endpoint(client, logged_in_headers):
"""Test flow creation via API."""
flow_data = {
"name": "Test Flow",
"description": "Test description",
"data": {"nodes": [], "edges": []}
}
response = await client.post(
"api/v1/flows/",
json=flow_data,
headers=logged_in_headers
)
assert response.status_code == codes.CREATED
flow = response.json()
assert flow["name"] == "Test Flow"
assert "id" in flow
```
### Build Config Testing
Test component configuration updates:
```python
async def test_build_config_updates(self, component_class, default_kwargs):
"""Test dynamic build configuration updates."""
component = await self.component_setup(component_class, default_kwargs)
frontend_node = component.to_frontend_node()
build_config = frontend_node["data"]["node"]["template"]
# Test configuration update
component.set(llm_provider="OpenAI")
updated_config = await component.update_build_config(
build_config, "OpenAI", "llm_provider"
)
assert updated_config["llm_provider"]["value"] == "OpenAI"
assert "gpt-4o" in updated_config["model_name"]["options"]
```
### Testing Event Streams
Test real-time event streaming endpoints:
```python
async def consume_and_validate_events(response, expected_job_id):
"""Consume NDJSON event stream and validate structure."""
count = 0
first_event_seen = False
end_event_seen = False
async for line in response.aiter_lines():
if not line:
continue
parsed = json.loads(line)
# Validate job_id in events
if "job_id" in parsed:
assert parsed["job_id"] == expected_job_id
# First event should be vertices_sorted
if not first_event_seen:
assert parsed["event"] == "vertices_sorted"
first_event_seen = True
# Track end event
elif parsed["event"] == "end":
end_event_seen = True
count += 1
assert first_event_seen and end_event_seen
return count
```
### Testing Component Versioning
Test backward compatibility across Langflow versions:
```python
@pytest.fixture
def file_names_mapping(self):
"""Map component files across Langflow versions."""
return [
VersionComponentMapping(
version="1.1.1",
module="inputs",
file_name="chat"
),
VersionComponentMapping(
version="1.1.0",
module="inputs",
file_name="chat"
),
VersionComponentMapping(
version="1.0.19",
module="inputs",
file_name="ChatInput"
),
]
```
### Webhook Testing
Test webhook endpoints with proper payload handling:
```python
async def test_webhook_endpoint(client, added_webhook_test):
"""Test webhook flow execution."""
endpoint_name = added_webhook_test["endpoint_name"]
endpoint = f"api/v1/webhook/{endpoint_name}"
payload = {"path": "/tmp/test_file.txt"}
response = await client.post(endpoint, json=payload)
assert response.status_code == 202
# Verify webhook processed the payload
# ... additional validation logic
```
### Monkeypatch for Testing Errors
Test error handling by mocking internal functions:
```python
async def test_cancellation_error(client, flow_id, logged_in_headers, monkeypatch):
"""Test error handling during flow cancellation."""
import langflow.api.v1.chat
async def mock_cancel_with_error(*args, **kwargs):
raise RuntimeError("Cancellation failed")
monkeypatch.setattr(
langflow.api.v1.chat,
"cancel_flow_build",
mock_cancel_with_error
)
response = await client.post(
f"api/v1/build/{job_id}/cancel",
headers=logged_in_headers
)
assert response.status_code == codes.INTERNAL_SERVER_ERROR
assert "Cancellation failed" in response.json()["detail"]
```