mirror of
https://github.com/bitsofchris/openaugi-obsidian-plugin.git
synced 2026-07-22 05:46:42 +00:00
testing docs
This commit is contained in:
parent
2d3c5d75ec
commit
a69b52281d
3 changed files with 205 additions and 1 deletions
13
CLAUDE.md
13
CLAUDE.md
|
|
@ -72,7 +72,18 @@ There is no standalone `typecheck` script — `npm run build` runs `tsc -noEmit
|
|||
- No external runtime dependencies (only Obsidian API)
|
||||
|
||||
### Testing
|
||||
Currently no automated tests. Manual testing through Obsidian's developer console.
|
||||
|
||||
Automated test suite using Vitest with a mock Obsidian API. See [docs/TESTING.md](docs/TESTING.md) for full details.
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
npm test
|
||||
|
||||
# Watch mode
|
||||
npm run test:watch
|
||||
```
|
||||
|
||||
Tests cover: filename utils, OpenAI prompt building, link extraction, BFS traversal, content aggregation, file output, journal filtering, backlink discovery. New features should include test coverage.
|
||||
|
||||
## API Integration
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ This guide walks through the complete process of publishing a new OpenAugi plugi
|
|||
Before starting the release process:
|
||||
|
||||
- [ ] All features/fixes are merged to master
|
||||
- [ ] **Automated tests pass** (`npm test`) — see [TESTING.md](TESTING.md)
|
||||
- [ ] New features have test coverage (add to `tests/`)
|
||||
- [ ] Code builds without errors (`npm run build`)
|
||||
- [ ] Type checking passes (`npm run typecheck`)
|
||||
- [ ] Manual testing completed in test vault (`/Users/chris/zk-for-testing`)
|
||||
|
|
@ -110,6 +112,7 @@ Ensure all docs reflect the new release:
|
|||
|
||||
```bash
|
||||
# Full release flow (example for version 0.4.0)
|
||||
npm test
|
||||
npm run build
|
||||
npm run typecheck
|
||||
|
||||
|
|
|
|||
190
docs/TESTING.md
Normal file
190
docs/TESTING.md
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
# Testing Guide
|
||||
|
||||
Automated tests for the OpenAugi plugin. Run these before every release and when developing new features.
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests once
|
||||
npm test
|
||||
|
||||
# Watch mode (re-runs on file changes)
|
||||
npm run test:watch
|
||||
```
|
||||
|
||||
Tests run in under 1 second. No Obsidian app, no API keys, no manual setup needed.
|
||||
|
||||
## How It Works
|
||||
|
||||
The plugin runs inside Obsidian's Electron environment, but our tests don't need Obsidian at all. Instead:
|
||||
|
||||
1. **Mock Obsidian API** (`tests/mocks/`) — A filesystem-backed mock that simulates `Vault`, `MetadataCache`, `App`, and `TFile` by reading real markdown files and parsing `[[wikilinks]]` to build link graphs.
|
||||
|
||||
2. **Test vault** (`tests/vault/`) — A small set of markdown files committed to the repo that represent various note structures (links, backlinks, journal dates, dataview blocks, checkboxes, etc.).
|
||||
|
||||
3. **Vitest** — Fast TypeScript test runner. Config in `vitest.config.ts`.
|
||||
|
||||
## Test Files
|
||||
|
||||
| File | What it tests |
|
||||
|------|--------------|
|
||||
| `tests/filename-utils.test.ts` | `sanitizeFilename`, `BacklinkMapper`, file collision handling |
|
||||
| `tests/openai-service.test.ts` | Prompt construction, context extraction, API response parsing |
|
||||
| `tests/distill-service.test.ts` | Link extraction, backlinks, content aggregation, journal filtering, dataview stripping |
|
||||
| `tests/file-service.test.ts` | File/folder creation, session folders, summaries, published posts |
|
||||
| `tests/context-gathering-service.test.ts` | BFS link traversal, depth limits, backlink discovery, character limits, folder exclusion |
|
||||
|
||||
## Adding Tests for a New Feature
|
||||
|
||||
### 1. Decide which test file
|
||||
|
||||
Match your feature to the service it lives in:
|
||||
|
||||
| If you changed... | Add tests to... |
|
||||
|-------------------|-----------------|
|
||||
| `src/utils/filename-utils.ts` | `tests/filename-utils.test.ts` |
|
||||
| `src/services/openai-service.ts` | `tests/openai-service.test.ts` |
|
||||
| `src/services/distill-service.ts` | `tests/distill-service.test.ts` |
|
||||
| `src/services/file-service.ts` | `tests/file-service.test.ts` |
|
||||
| `src/services/context-gathering-service.ts` | `tests/context-gathering-service.test.ts` |
|
||||
| New service | Create `tests/your-service.test.ts` |
|
||||
|
||||
### 2. Add test vault fixtures (if needed)
|
||||
|
||||
If your feature needs specific note content to test against, add markdown files to `tests/vault/`:
|
||||
|
||||
```
|
||||
tests/vault/
|
||||
├── Root Note.md # Forward links to A and B
|
||||
├── Linked Note A.md # Links to Deep Note
|
||||
├── Linked Note B.md # Links back to A
|
||||
├── Backlink Source.md # Links TO Root Note
|
||||
├── Journal Note.md # Date headers (### YYYY-MM-DD)
|
||||
├── Dataview Note.md # ```dataview blocks
|
||||
├── Collection Note.md # Checkbox links [x] / [ ]
|
||||
├── Context Note.md # context: section
|
||||
├── Special Characters!.md # Filename sanitization edge case
|
||||
├── Deeply Linked/
|
||||
│ └── Deep Note.md # Depth-2 traversal
|
||||
└── Excluded Folder/
|
||||
└── Should Skip.md # Folder exclusion
|
||||
```
|
||||
|
||||
After adding a new fixture file, the mock `MetadataCache` automatically picks it up — it scans all `.md` files and parses their `[[links]]` on initialization.
|
||||
|
||||
### 3. Write the test
|
||||
|
||||
Pattern for **pure unit tests** (no Obsidian needed):
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { myFunction } from '../src/utils/my-utils';
|
||||
|
||||
describe('myFunction', () => {
|
||||
it('does the expected thing', () => {
|
||||
expect(myFunction('input')).toBe('output');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Pattern for **integration tests** (using mock Obsidian API):
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { MyService } from '../src/services/my-service';
|
||||
import { createMockApp, createTestTFile } from './mocks/obsidian-mock';
|
||||
import * as path from 'path';
|
||||
|
||||
const VAULT_DIR = path.resolve(__dirname, 'vault');
|
||||
|
||||
describe('MyService', () => {
|
||||
let app: ReturnType<typeof createMockApp>;
|
||||
let service: MyService;
|
||||
|
||||
beforeEach(() => {
|
||||
app = createMockApp(VAULT_DIR);
|
||||
service = new MyService(app as any, /* other deps */);
|
||||
});
|
||||
|
||||
it('discovers linked notes', async () => {
|
||||
const rootFile = createTestTFile(VAULT_DIR, 'Root Note.md');
|
||||
const result = await service.someMethod(rootFile);
|
||||
expect(result).toContain('expected');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Pattern for **testing OpenAI calls** (mock `fetch`):
|
||||
|
||||
```typescript
|
||||
import { vi } from 'vitest';
|
||||
|
||||
it('calls API correctly', async () => {
|
||||
const mockResponse = {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
choices: [{ message: { content: '{"summary":"test"}', refusal: null } }]
|
||||
})
|
||||
};
|
||||
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse as any);
|
||||
|
||||
const result = await service.someApiCall('input');
|
||||
expect(result.summary).toBe('test');
|
||||
|
||||
fetchSpy.mockRestore();
|
||||
});
|
||||
```
|
||||
|
||||
Pattern for **file output tests** (uses temp directories):
|
||||
|
||||
```typescript
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { MockApp } from './mocks/obsidian-mock';
|
||||
|
||||
const BASE_OUTPUT_DIR = path.resolve(__dirname, 'vault-output');
|
||||
let outputDir: string;
|
||||
let counter = 0;
|
||||
|
||||
beforeEach(() => {
|
||||
counter++;
|
||||
outputDir = path.join(BASE_OUTPUT_DIR, `run-${counter}-${Date.now()}`);
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
// Point MockApp at the output dir, not the fixture vault
|
||||
app = new MockApp(outputDir);
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Run and verify
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
## What's NOT Tested (and why)
|
||||
|
||||
| Area | Reason |
|
||||
|------|--------|
|
||||
| UI modals | Tightly coupled to Obsidian DOM — test manually |
|
||||
| Task dispatch (tmux) | Requires system-level tmux — test manually |
|
||||
| Dataview plugin queries | Requires running Dataview plugin — mock returns empty |
|
||||
| Real OpenAI API calls | Costs money — we mock `fetch` instead |
|
||||
|
||||
## Mock Obsidian API Reference
|
||||
|
||||
The mock (`tests/mocks/obsidian-mock.ts`) supports:
|
||||
|
||||
| Obsidian API | Mock behavior |
|
||||
|-------------|---------------|
|
||||
| `vault.read(file)` | Reads from filesystem |
|
||||
| `vault.create(path, content)` | Writes to filesystem |
|
||||
| `vault.createFolder(path)` | `mkdir -p` |
|
||||
| `vault.getMarkdownFiles()` | Walks directory tree |
|
||||
| `vault.getAbstractFileByPath(path)` | `fs.existsSync` lookup |
|
||||
| `vault.adapter.exists(path)` | `fs.existsSync` |
|
||||
| `vault.adapter.stat(path)` | `fs.statSync` |
|
||||
| `metadataCache.getFileCache(file)` | Parses `[[links]]` from file content |
|
||||
| `metadataCache.getFirstLinkpathDest(link, source)` | Resolves links by basename matching |
|
||||
| `metadataCache.resolvedLinks` | Pre-built link graph from all vault files |
|
||||
|
||||
If a test needs an API that isn't mocked, add it to `tests/mocks/obsidian-mock.ts`.
|
||||
Loading…
Reference in a new issue