feat: add word count support for read operations
Extended word count functionality to read operations (read_note, stat, list) to complement existing write operation support. Changes: - read_note: Now automatically includes wordCount when returning content (with withContent or parseFrontmatter options) - stat: Added optional includeWordCount parameter with performance warning - list: Added optional includeWordCount parameter with performance warning - All operations use same word counting rules (excludes frontmatter and Obsidian comments) - Best-effort error handling for batch operations Technical details: - Updated ParsedNote and FileMetadata type definitions to include optional wordCount field - Added comprehensive test coverage (18 new tests) - Updated tool descriptions with usage notes and performance warnings - Updated CHANGELOG.md to document new features in version 1.1.0
This commit is contained in:
@@ -18,6 +18,18 @@ jest.mock('../src/utils/path-utils', () => ({
|
||||
}
|
||||
}));
|
||||
|
||||
// Mock LinkUtils for link validation tests
|
||||
jest.mock('../src/utils/link-utils', () => ({
|
||||
LinkUtils: {
|
||||
validateLinks: jest.fn().mockResolvedValue({
|
||||
valid: [],
|
||||
brokenNotes: [],
|
||||
brokenHeadings: [],
|
||||
summary: 'No links found'
|
||||
})
|
||||
}
|
||||
}));
|
||||
|
||||
// Import the mocked PathUtils
|
||||
import { PathUtils } from '../src/utils/path-utils';
|
||||
|
||||
@@ -50,7 +62,10 @@ describe('NoteTools', () => {
|
||||
const result = await noteTools.readNote('test.md');
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
expect(result.content[0].text).toBe(content);
|
||||
// Now returns JSON with content and wordCount
|
||||
const parsed = JSON.parse(result.content[0].text);
|
||||
expect(parsed.content).toBe(content);
|
||||
expect(parsed.wordCount).toBe(7); // Test Note This is test content
|
||||
expect(mockVault.read).toHaveBeenCalledWith(mockFile);
|
||||
});
|
||||
|
||||
@@ -101,6 +116,93 @@ describe('NoteTools', () => {
|
||||
// frontmatter field is the raw YAML string
|
||||
expect(parsed.frontmatter).toBeDefined();
|
||||
});
|
||||
|
||||
it('should include word count when withContent is true', async () => {
|
||||
const mockFile = createMockTFile('test.md');
|
||||
const content = '# Test Note\n\nThis is a test note with some words.';
|
||||
|
||||
(PathUtils.resolveFile as jest.Mock).mockReturnValue(mockFile);
|
||||
mockVault.read = jest.fn().mockResolvedValue(content);
|
||||
|
||||
const result = await noteTools.readNote('test.md', { withContent: true });
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
const parsed = JSON.parse(result.content[0].text);
|
||||
expect(parsed.content).toBe(content);
|
||||
expect(parsed.wordCount).toBe(11); // Test Note This is a test note with some words
|
||||
});
|
||||
|
||||
it('should include word count when parseFrontmatter is true', async () => {
|
||||
const mockFile = createMockTFile('test.md');
|
||||
const content = '---\ntitle: Test\n---\n\nThis is content.';
|
||||
|
||||
(PathUtils.resolveFile as jest.Mock).mockReturnValue(mockFile);
|
||||
mockVault.read = jest.fn().mockResolvedValue(content);
|
||||
|
||||
const result = await noteTools.readNote('test.md', { parseFrontmatter: true });
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
const parsed = JSON.parse(result.content[0].text);
|
||||
expect(parsed.wordCount).toBe(3); // "This is content."
|
||||
});
|
||||
|
||||
it('should exclude frontmatter from word count', async () => {
|
||||
const mockFile = createMockTFile('test.md');
|
||||
const content = '---\ntitle: Test Note\ntags: [test, example]\n---\n\nActual content words.';
|
||||
|
||||
(PathUtils.resolveFile as jest.Mock).mockReturnValue(mockFile);
|
||||
mockVault.read = jest.fn().mockResolvedValue(content);
|
||||
|
||||
const result = await noteTools.readNote('test.md', { parseFrontmatter: true });
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
const parsed = JSON.parse(result.content[0].text);
|
||||
expect(parsed.wordCount).toBe(3); // "Actual content words."
|
||||
});
|
||||
|
||||
it('should exclude Obsidian comments from word count', async () => {
|
||||
const mockFile = createMockTFile('test.md');
|
||||
const content = 'Visible text %% Hidden comment %% more visible.';
|
||||
|
||||
(PathUtils.resolveFile as jest.Mock).mockReturnValue(mockFile);
|
||||
mockVault.read = jest.fn().mockResolvedValue(content);
|
||||
|
||||
const result = await noteTools.readNote('test.md', { withContent: true });
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
const parsed = JSON.parse(result.content[0].text);
|
||||
expect(parsed.wordCount).toBe(4); // "Visible text more visible"
|
||||
});
|
||||
|
||||
it('should return 0 word count for empty file', async () => {
|
||||
const mockFile = createMockTFile('empty.md');
|
||||
const content = '';
|
||||
|
||||
(PathUtils.resolveFile as jest.Mock).mockReturnValue(mockFile);
|
||||
mockVault.read = jest.fn().mockResolvedValue(content);
|
||||
|
||||
const result = await noteTools.readNote('empty.md', { withContent: true });
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
const parsed = JSON.parse(result.content[0].text);
|
||||
expect(parsed.wordCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should return JSON format even with default options', async () => {
|
||||
const mockFile = createMockTFile('test.md');
|
||||
const content = '# Test Note\n\nContent here.';
|
||||
|
||||
(PathUtils.resolveFile as jest.Mock).mockReturnValue(mockFile);
|
||||
mockVault.read = jest.fn().mockResolvedValue(content);
|
||||
|
||||
const result = await noteTools.readNote('test.md');
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
// Now returns JSON even with default options
|
||||
const parsed = JSON.parse(result.content[0].text);
|
||||
expect(parsed.content).toBe(content);
|
||||
expect(parsed.wordCount).toBe(5); // Test Note Content here
|
||||
});
|
||||
});
|
||||
|
||||
describe('createNote', () => {
|
||||
|
||||
Reference in New Issue
Block a user