Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions MOCKING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Mock Mode Documentation

This document explains how to use mock mode for local development and testing of React Grab's Visual Edit feature.

## Overview

Mock mode enables local development and E2E testing without requiring external API dependencies like the Cerebras AI API. When enabled, all requests to external services are intercepted and handled by local mock endpoints that return pattern-based responses.

## Setup

### 1. Enable Mock Mode

Create a `.env.local` file in `packages/website/` (or copy from `.env.local.example`):

```bash
USE_MOCKS=true
```

### 2. Start Development Server

```bash
nr dev
```

The development server will automatically detect mock mode and route all external API requests to mock endpoints.

## How It Works

### Mock Endpoint

When mock mode is enabled, the `/api/mock/cerebras` endpoint becomes available. This endpoint:

1. Only responds when `USE_MOCKS=true`
2. Returns a `403 Forbidden` error when accessed outside mock mode
3. Generates pattern-based JavaScript code matching the real Cerebras API format
4. Simulates a 100ms delay for realistic behavior

### Pattern-Based Code Generation

The mock endpoint recognizes keywords in user requests and generates appropriate DOM manipulation code:

| Pattern | Keywords | Generated Code |
|---------|----------|----------------|
| Text Change | text, content | Changes element text content |
| Class Modification | class, style | Adds CSS classes |
| Hide Element | hide, remove | Hides element via display:none |
| Color Change | color, background, bg | Modifies element colors |
| Animation | animate, animation, fade, slide | Adds CSS animations |
| Default | (other) | Sets data attribute |

### Visual Edit Endpoint Integration

The `/api/visual-edit` endpoint automatically detects mock mode:

```typescript
if (isMockMode()) {
// Route to mock endpoint
const mockResponse = await fetch("/api/mock/cerebras", {
method: "POST",
body: JSON.stringify({ messages }),
});
} else {
// Use real Cerebras API
const result = await generateText({
model: "cerebras/glm-4.6",
system: SYSTEM_PROMPT,
messages,
});
}
```

### Health Check

The `/api/check-visual-edit` endpoint reports mock mode status:

```json
{
"healthy": true,
"mockMode": true
}
```

## Disabling Mock Mode

To disable mock mode and use real external services:

1. Set `USE_MOCKS=false` in `.env.local` (or remove the variable)
2. Restart the development server
3. Configure required API keys for external services

## Testing

### Manual Testing

1. Enable mock mode: `USE_MOCKS=true`
2. Start dev server: `nr dev`
3. Test direct mock endpoint:
```bash
curl -X POST http://localhost:3000/api/mock/cerebras \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "change text to loading"}]}'
```
4. Test via visual-edit endpoint:
```bash
curl -X POST http://localhost:3000/api/visual-edit \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "add animation"}]}'
```
5. Check mock status:
```bash
curl http://localhost:3000/api/check-visual-edit
```

### E2E Testing

Mock mode is automatically enabled in E2E test environments. Playwright tests will use mock endpoints by default.

## Production Safety

Mock mode is designed to NEVER be available in production:

1. The `/api/mock/cerebras` endpoint returns `403 Forbidden` when `USE_MOCKS=false`
2. Environment variables should never set `USE_MOCKS=true` in production
3. The `isMockMode()` function checks environment variables at runtime

## Troubleshooting

### Mock endpoint returns 403

Ensure `USE_MOCKS=true` is set in `.env.local` and restart the dev server.

### Mock responses don't match expected patterns

Check the mock state manager in `lib/mock-cerebras-state.ts` and verify keyword matching logic.

### Real API still being called

Verify that `isMockMode()` returns `true` and check console logs for the mock mode status.

## Implementation Details

### Files

- `lib/env.ts` - Global mock toggle and service resolution
- `lib/mock-cerebras-state.ts` - Singleton in-memory state manager
- `app/api/mock/cerebras/route.ts` - Mock endpoint implementation
- `app/api/visual-edit/route.ts` - Updated to support mock mode
- `app/api/check-visual-edit/route.ts` - Reports mock status

### State Management

Mock state is maintained in-memory using a singleton pattern. Request history is stored in a `Map` structure and can be accessed via:

```typescript
import { getMockRequestHistory, clearMockState } from "@/lib/mock-cerebras-state";

const history = getMockRequestHistory();
clearMockState();
```
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,61 @@ export default function RootLayout({ children }) {

</details>

## Mock Development

React Grab supports mock mode for local development and E2E testing without external API dependencies.

### Enable Mock Mode

Create `.env.local` in `packages/website/`:

```bash
USE_MOCKS=true
```

Then start the development server:

```bash
nr dev
```

### How Mock Mode Works

When enabled, all external API requests (like Cerebras AI) are routed to local mock endpoints that return pattern-based responses. This enables:

- Local development without API keys
- E2E testing without external dependencies
- Faster iteration cycles
- Consistent test results

Mock endpoints recognize keywords in requests and generate appropriate DOM manipulation code:

- **Text changes**: "change text", "update content"
- **CSS classes**: "add class", "apply style"
- **Visibility**: "hide element", "remove"
- **Colors**: "change color", "set background"
- **Animations**: "add animation", "fade in"

### Check Mock Status

```bash
curl http://localhost:3000/api/check-visual-edit
```

Response:
```json
{
"healthy": true,
"mockMode": true
}
```

### Disable Mock Mode

Set `USE_MOCKS=false` in `.env.local` or remove the variable, then restart the server.

For detailed documentation, see [MOCKING.md](MOCKING.md).

## Extending React Grab

React Grab provides an public customization API. Check out the [type definitions](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/types.ts) to see all available options for extending React Grab.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"scripts": {
"build": "turbo run build --filter=react-grab --filter=@react-grab/cursor --filter=@react-grab/claude-code --filter=@react-grab/ami --filter=@react-grab/opencode --filter=@react-grab/codex --filter=@react-grab/gemini --filter=@react-grab/amp --filter=@react-grab/cli --filter=@react-grab/utils --filter=@react-grab/visual-edit && pnpm --filter grab build",
"dev": "turbo dev --filter=react-grab --filter=@react-grab/cursor --filter=@react-grab/claude-code --filter=@react-grab/ami --filter=@react-grab/opencode --filter=@react-grab/codex --filter=@react-grab/gemini --filter=@react-grab/amp --filter=@react-grab/cli --filter=@react-grab/utils --filter=@react-grab/visual-edit",
"test": "turbo run test --filter=react-grab --filter=@react-grab/cli",
"test": "turbo run test --filter=react-grab --filter=@react-grab/cli --filter=@react-grab/website",
"lint": "pnpm --filter react-grab lint",
"lint:fix": "pnpm --filter react-grab lint:fix",
"format": "prettier --write .",
Expand Down
55 changes: 55 additions & 0 deletions packages/grab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,61 @@ export default function RootLayout({ children }) {

</details>

## Mock Development

React Grab supports mock mode for local development and E2E testing without external API dependencies.

### Enable Mock Mode

Create `.env.local` in `packages/website/`:

```bash
USE_MOCKS=true
```

Then start the development server:

```bash
nr dev
```

### How Mock Mode Works

When enabled, all external API requests (like Cerebras AI) are routed to local mock endpoints that return pattern-based responses. This enables:

- Local development without API keys
- E2E testing without external dependencies
- Faster iteration cycles
- Consistent test results

Mock endpoints recognize keywords in requests and generate appropriate DOM manipulation code:

- **Text changes**: "change text", "update content"
- **CSS classes**: "add class", "apply style"
- **Visibility**: "hide element", "remove"
- **Colors**: "change color", "set background"
- **Animations**: "add animation", "fade in"

### Check Mock Status

```bash
curl http://localhost:3000/api/check-visual-edit
```

Response:
```json
{
"healthy": true,
"mockMode": true
}
```

### Disable Mock Mode

Set `USE_MOCKS=false` in `.env.local` or remove the variable, then restart the server.

For detailed documentation, see [MOCKING.md](MOCKING.md).

## Extending React Grab

React Grab provides an public customization API. Check out the [type definitions](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/types.ts) to see all available options for extending React Grab.
Expand Down
5 changes: 4 additions & 1 deletion packages/website/app/api/check-visual-edit/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isMockMode } from "@/lib/env";

const IS_HEALTHY = true;

const getCorsHeaders = () => {
Expand All @@ -15,8 +17,9 @@ export const OPTIONS = () => {

export const GET = () => {
const corsHeaders = getCorsHeaders();
const mockModeEnabled = isMockMode();
return Response.json(
{ healthy: IS_HEALTHY },
{ healthy: IS_HEALTHY, mockMode: mockModeEnabled },
{ headers: { ...corsHeaders, "Content-Type": "application/json" } },
);
};
76 changes: 76 additions & 0 deletions packages/website/app/api/mock/cerebras/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { isMockMode } from "@/lib/env";
import {
generateMockResponse,
storeMockRequest,
} from "@/lib/mock-cerebras-state";

interface ConversationMessage {
role: "user" | "assistant";
content: string;
}

const getCorsHeaders = () => {
return {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
};

export const OPTIONS = () => {
const corsHeaders = getCorsHeaders();
return new Response(null, { status: 204, headers: corsHeaders });
};

export const POST = async (request: Request) => {
const corsHeaders = getCorsHeaders();

if (!isMockMode()) {
return new Response(
JSON.stringify({
error: "Mock endpoint not available outside mock mode",
}),
{
status: 403,
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
}

let body: { messages: ConversationMessage[] };
try {
body = await request.json();
} catch {
return new Response(JSON.stringify({ error: "Invalid JSON" }), {
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}

const { messages } = body;

if (!messages || messages.length === 0) {
return new Response(
JSON.stringify({ error: "messages array is required" }),
{
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
}

const requestId = `mock-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
storeMockRequest(requestId, messages);

const mockCode = generateMockResponse(messages);

await new Promise((resolve) => setTimeout(resolve, 100));

return new Response(mockCode, {
headers: {
...corsHeaders,
"Content-Type": "text/javascript",
"Cache-Control": "no-cache, no-store, must-revalidate",
},
});
};
Loading