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:
parent
34c55f14b4
commit
ab1ed8ea01
6 changed files with 1670 additions and 17 deletions
268
.cursor/rules/backend_development.mdc
Normal file
268
.cursor/rules/backend_development.mdc
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
440
.cursor/rules/docs_development.mdc
Normal file
440
.cursor/rules/docs_development.mdc
Normal 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/ -->
|
||||

|
||||
|
||||
<!-- Use descriptive alt text -->
|
||||

|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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`
|
||||
374
.cursor/rules/frontend_development.mdc
Normal file
374
.cursor/rules/frontend_development.mdc
Normal 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
|
||||
|
||||
|
|
@ -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
561
.cursor/rules/testing.mdc
Normal 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"]
|
||||
```
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue