Skip to content
Merged
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
72 changes: 23 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@
[![npm version](https://img.shields.io/npm/v/react-native-testsmith.svg)](https://www.npmjs.com/package/react-native-testsmith)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Production-ready, local-first CLI for React Native unit testing with Jest and React Native Testing Library.
Production-ready CLI for React Native unit testing with Jest and React Native Testing Library.

## 1.0.1 Update

- Switched to API-first AI generation (Ollama removed on this branch)
- `init` now runs Jest setup automatically
- `generate` now runs full pipeline: scan + API check + per-file AI generation
- Long files are chunked automatically for free-tier model limits
- Added per-file progress logs and final generation summary counts

## Why this exists

- Automates painful Jest + RN config
- Scans your components/screens and builds metadata
- Generates stable test templates quickly
- Keeps everything local (no external API needed)
- Supports API-driven AI test generation with chunking for large files

## Install

Expand Down Expand Up @@ -39,14 +47,7 @@ If you see `command not found`, install globally (`npm i -g .`) or run with `npx

```bash
react-native-testsmith init
react-native-testsmith setup
react-native-testsmith setup --dry-run
react-native-testsmith setup --with-native-mocks
react-native-testsmith setup --skip-install
react-native-testsmith scan
react-native-testsmith generate
react-native-testsmith ai-setup
react-native-testsmith ai-enhance --target src/screens/LoginScreen.tsx
react-native-testsmith doctor
react-native-testsmith doctor --json
```
Expand All @@ -55,11 +56,7 @@ react-native-testsmith doctor --json

```bash
react-native-testsmith init
react-native-testsmith setup
react-native-testsmith scan
react-native-testsmith generate
react-native-testsmith ai-setup
react-native-testsmith ai-enhance --target src/screens/LoginScreen.tsx --apply --run-jest
```

## Full setup steps (end-to-end)
Expand All @@ -85,34 +82,18 @@ react-native-testsmith init
4. Configure Jest and required mocks

```bash
# already done by init
# optional manual run:
react-native-testsmith setup
```

5. Scan project components/screens

```bash
react-native-testsmith scan
```

6. Generate baseline test templates
5. Run complete generation pipeline (scan + API check + per-file AI generation)

```bash
react-native-testsmith generate
```

7. Bootstrap local AI runtime (Ollama + model)

```bash
react-native-testsmith ai-setup
```

8. Generate/improve tests with AI for a specific file

```bash
react-native-testsmith ai-enhance --target src/screens/LoginScreen.tsx --apply --run-jest
```

9. Validate setup health
6. Verify setup health

```bash
react-native-testsmith doctor
Expand All @@ -136,6 +117,8 @@ When you pass `--with-native-mocks`, setup also creates:

Use `--dry-run` to preview all setup changes safely before writing files.

`init` runs setup automatically. You can still run `setup` directly when needed.

## Test output structure

`generate` mirrors your source structure in `__tests__` when `testFileStyle` is `tests-dir`.
Expand All @@ -144,22 +127,13 @@ Example:
- `src/components/Button.tsx` -> `__tests__/components/Button.test.tsx`
- `src/screens/auth/Login.js` -> `__tests__/screens/auth/Login.test.js`

## Local AI enhancement

`ai-enhance` uses a local Ollama model (no external API):

```bash
react-native-testsmith ai-setup
react-native-testsmith ai-enhance --target src/screens/LoginScreen.tsx --apply
react-native-testsmith ai-enhance --target src/screens/LoginScreen.tsx --model qwen2.5-coder:7b --apply --run-jest
```
## API notes

Notes:
- `ai-setup` checks Ollama, starts service if needed, and downloads model automatically.
- First-time model download may take several minutes. This is one-time per model.
- Without `--apply`, the command runs in preview mode and prints output.
- If `--run-jest` is set and Jest fails, AI auto-fix retries run (based on `ai.maxRetries`).
- We are actively working on a cloud API mode for complete automatic testing across whole projects.
- `generate` is the primary AI command now (project-wide).
- `RN_TESTSMITH_API_URL` overrides endpoint and `RN_TESTSMITH_API_KEY` is optional.
- Long files are chunked automatically and then synthesized.
- `scan` includes `App.ts`, `App.js`, `App.tsx`, and `App.jsx` at project root.
- Terminal output includes per-file progress and final AI response/generated counts.

## CI

Expand All @@ -175,7 +149,7 @@ GitHub Actions CI is included at `.github/workflows/ci.yml`:

## Sponsor request

`react-native-testsmith` is currently local-first and free to use.
`react-native-testsmith` is currently free to use.

If this project saves your team time, please sponsor development so we can:
- maintain and improve templates faster
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-testsmith",
"version": "0.1.0",
"version": "0.1.1",
"description": "Local-first React Native testing CLI for Jest and Testing Library",
"license": "MIT",
"type": "module",
Expand Down
106 changes: 106 additions & 0 deletions src/ai/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { AiProvider } from "./provider.js";

type ApiTextResponse = {
response?: string;
text?: string;
output?: string;
data?: {
response?: string;
text?: string;
output?: string;
};
};

function extractApiText(payload: unknown): string {
if (typeof payload === "string") return payload;
if (!payload || typeof payload !== "object") return "";
const p = payload as ApiTextResponse;
return p.response ?? p.text ?? p.output ?? p.data?.response ?? p.data?.text ?? p.data?.output ?? "";
}

async function callApi(endpoint: string, bodyText: string, apiKey?: string): Promise<string> {
const res = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "text/plain",
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})
},
body: bodyText
});

if (!res.ok) {
throw new Error(`API request failed: ${res.status} ${res.statusText}`);
}

const contentType = res.headers.get("content-type") ?? "";
if (contentType.includes("application/json")) {
const json = await res.json();
const text = extractApiText(json);
if (!text) throw new Error("API response did not include a text payload.");
return text;
}

return res.text();
}

function chunkText(input: string, chunkSize: number): string[] {
if (input.length <= chunkSize) return [input];
const chunks: string[] = [];
for (let i = 0; i < input.length; i += chunkSize) {
chunks.push(input.slice(i, i + chunkSize));
}
return chunks;
}

export function createApiProvider(): AiProvider {
return {
runtime: "api",
async generateText({ prompt, model }) {
const endpoint = process.env.RN_TESTSMITH_API_URL ?? "https://aashir321-faraz-ai-model.hf.space/generate-tests";
const apiKey = process.env.RN_TESTSMITH_API_KEY;
if (!endpoint) {
throw new Error("RN_TESTSMITH_API_URL is not set.");
}

const chunkSize = Number(process.env.RN_TESTSMITH_API_CHUNK_SIZE ?? "12000");
const chunks = chunkText(prompt, chunkSize);

if (chunks.length === 1) {
return callApi(endpoint, chunks[0], apiKey);
}

const analyses: string[] = [];
for (let i = 0; i < chunks.length; i += 1) {
const chunkPrompt = `Model: ${model}
You are receiving chunk ${i + 1}/${chunks.length} from a large React Native component/test request.
Return concise notes for this chunk covering:
- rendered UI and text labels
- state/hooks/effects
- handlers/user interactions
- async/api calls and mocks
- navigation/redux usage

Chunk:
${chunks[i]}`;
analyses.push(await callApi(endpoint, chunkPrompt, apiKey));
}

const synthesisPrompt = `Model: ${model}
You are given chunk analyses from a large React Native input.
Produce final output in this format:
### Component Summary
...
### Key Test Scenarios
- ...
### Full Test File
\`\`\`tsx
...full test file...
\`\`\`

Chunk analyses:
${analyses.map((a, idx) => `--- Chunk ${idx + 1} ---\n${a}`).join("\n\n")}`;

return callApi(endpoint, synthesisPrompt, apiKey);
}
};
}
55 changes: 0 additions & 55 deletions src/ai/ollama.ts

This file was deleted.

11 changes: 11 additions & 0 deletions src/ai/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type AiProviderRuntime = "api";

export type GenerateTextInput = {
prompt: string;
model: string;
};

export type AiProvider = {
runtime: AiProviderRuntime;
generateText: (input: GenerateTextInput) => Promise<string>;
};
15 changes: 7 additions & 8 deletions src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ program

program
.command("setup")
.description("Create Jest + React Native setup files")
.description("Create Jest + React Native setup files (optional; init runs this automatically)")
.option("-f, --force", "overwrite existing setup files")
.option("--dry-run", "preview file changes without writing")
.option("--with-native-mocks", "also add common native module mocks")
Expand All @@ -39,22 +39,21 @@ program

program
.command("generate")
.description("Generate test templates from scan report")
.description("Run full AI generation pipeline for all scanned files")
.option("-f, --force", "overwrite existing test files")
.action((options) => runGenerate(projectRoot, options));
.action(async (options) => runGenerate(projectRoot, options));

program
.command("ai-setup")
.description("Install/check local AI runtime requirements (Ollama + model)")
.option("-m, --model <name>", "Ollama model name override")
.option("--skip-pull", "skip model download and only run checks")
.description("Check API runtime requirements")
.option("-e, --endpoint <url>", "API endpoint override for setup check")
.action(async (options) => runAiSetup(projectRoot, options));

program
.command("ai-enhance")
.description("Generate or improve tests with local Ollama model")
.description("Generate or improve tests with API runtime")
.requiredOption("-t, --target <path>", "target component path")
.option("-m, --model <name>", "Ollama model name override")
.option("-m, --model <name>", "model name forwarded to API")
.option("--apply", "write generated test file")
.option("-f, --force", "overwrite existing generated test file")
.option("--run-jest", "run scoped jest for generated file")
Expand Down
Loading
Loading