mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-06-22 10:58:03 +08:00
Expand core tests and add vitest coverage dep
Add several unit tests to improve robustness and coverage across the core package: plugin-discovery (handle missing/invalid plugins field and add serializePluginConfig tests), plugin-registry (unregister behavior, language detection, and import resolution delegation), persistence (invalid graph validation and option to skip validation), and tree-sitter-plugin (export aliases and arrow functions without params). Also add @vitest/coverage-v8 as a devDependency to enable V8-based coverage reporting.
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.0",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.1.0"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
parsePluginConfig,
|
||||
serializePluginConfig,
|
||||
type PluginConfig,
|
||||
type PluginEntry,
|
||||
DEFAULT_PLUGIN_CONFIG,
|
||||
@@ -53,6 +54,22 @@ describe("plugin-discovery", () => {
|
||||
const config = parsePluginConfig(json);
|
||||
expect(config.plugins[0].enabled).toBe(true);
|
||||
});
|
||||
|
||||
it("returns default config when plugins field is not an array", () => {
|
||||
const json = JSON.stringify({
|
||||
plugins: "not an array",
|
||||
});
|
||||
const config = parsePluginConfig(json);
|
||||
expect(config).toEqual(DEFAULT_PLUGIN_CONFIG);
|
||||
});
|
||||
|
||||
it("returns default config when plugins field is missing", () => {
|
||||
const json = JSON.stringify({
|
||||
someOtherField: "value",
|
||||
});
|
||||
const config = parsePluginConfig(json);
|
||||
expect(config).toEqual(DEFAULT_PLUGIN_CONFIG);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DEFAULT_PLUGIN_CONFIG", () => {
|
||||
@@ -62,4 +79,38 @@ describe("plugin-discovery", () => {
|
||||
expect(DEFAULT_PLUGIN_CONFIG.plugins[0].enabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("serializePluginConfig", () => {
|
||||
it("serializes plugin config to formatted JSON", () => {
|
||||
const config: PluginConfig = {
|
||||
plugins: [
|
||||
{
|
||||
name: "tree-sitter",
|
||||
enabled: true,
|
||||
languages: ["typescript", "javascript"],
|
||||
},
|
||||
],
|
||||
};
|
||||
const json = serializePluginConfig(config);
|
||||
expect(json).toContain('"name": "tree-sitter"');
|
||||
expect(json).toContain('"enabled": true');
|
||||
expect(json).toContain('"languages"');
|
||||
});
|
||||
|
||||
it("serializes config with options field", () => {
|
||||
const config: PluginConfig = {
|
||||
plugins: [
|
||||
{
|
||||
name: "custom-plugin",
|
||||
enabled: true,
|
||||
languages: ["python"],
|
||||
options: { strict: true, timeout: 5000 },
|
||||
},
|
||||
],
|
||||
};
|
||||
const json = serializePluginConfig(config);
|
||||
expect(json).toContain('"options"');
|
||||
expect(json).toContain('"strict": true');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,4 +110,68 @@ describe("PluginRegistry", () => {
|
||||
const result = registry.analyzeFile("main.py", "print('hello')");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("unregister rebuilds language map correctly", () => {
|
||||
const registry = new PluginRegistry();
|
||||
const plugin1 = createMockPlugin("plugin1", ["typescript", "javascript"]);
|
||||
const plugin2 = createMockPlugin("plugin2", ["python"]);
|
||||
|
||||
registry.register(plugin1);
|
||||
registry.register(plugin2);
|
||||
|
||||
expect(registry.getPluginForLanguage("typescript")).toBe(plugin1);
|
||||
expect(registry.getPluginForLanguage("python")).toBe(plugin2);
|
||||
|
||||
registry.unregister("plugin1");
|
||||
|
||||
expect(registry.getPluginForLanguage("typescript")).toBeNull();
|
||||
expect(registry.getPluginForLanguage("python")).toBe(plugin2);
|
||||
});
|
||||
|
||||
it("unregister does nothing for non-existent plugin", () => {
|
||||
const registry = new PluginRegistry();
|
||||
const plugin = createMockPlugin("existing", ["typescript"]);
|
||||
registry.register(plugin);
|
||||
|
||||
registry.unregister("non-existent");
|
||||
|
||||
expect(registry.getPlugins()).toHaveLength(1);
|
||||
expect(registry.getPluginForLanguage("typescript")).toBe(plugin);
|
||||
});
|
||||
|
||||
it("getLanguageForFile returns correct language id", () => {
|
||||
const registry = new PluginRegistry();
|
||||
registry.register(createMockPlugin("ts-plugin", ["typescript"]));
|
||||
|
||||
expect(registry.getLanguageForFile("src/index.ts")).toBe("typescript");
|
||||
expect(registry.getLanguageForFile("src/component.tsx")).toBe("typescript");
|
||||
});
|
||||
|
||||
it("getLanguageForFile returns null for unsupported extensions", () => {
|
||||
const registry = new PluginRegistry();
|
||||
registry.register(createMockPlugin("ts-plugin", ["typescript"]));
|
||||
|
||||
expect(registry.getLanguageForFile("unknown.xyz")).toBeNull();
|
||||
});
|
||||
|
||||
it("resolveImports delegates to correct plugin", () => {
|
||||
const registry = new PluginRegistry();
|
||||
const plugin = createMockPlugin("ts-plugin", ["typescript"]);
|
||||
const mockImports: ImportResolution[] = [
|
||||
{ importPath: "./utils", resolvedPath: "./utils.ts" },
|
||||
];
|
||||
plugin.resolveImports = () => mockImports;
|
||||
registry.register(plugin);
|
||||
|
||||
const result = registry.resolveImports("src/index.ts", "import './utils'");
|
||||
expect(result).toEqual(mockImports);
|
||||
});
|
||||
|
||||
it("resolveImports returns null for unsupported files", () => {
|
||||
const registry = new PluginRegistry();
|
||||
registry.register(createMockPlugin("ts-plugin", ["typescript"]));
|
||||
|
||||
const result = registry.resolveImports("main.py", "import os");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,6 +92,24 @@ describe("persistence", () => {
|
||||
const loaded = loadGraph(tempDir);
|
||||
expect(loaded).toBeNull();
|
||||
});
|
||||
|
||||
it("should throw error when loading invalid graph", () => {
|
||||
const invalidGraph = { ...sampleGraph, version: 123 }; // Invalid version type
|
||||
saveGraph(tempDir, invalidGraph as unknown as KnowledgeGraph);
|
||||
|
||||
expect(() => {
|
||||
loadGraph(tempDir);
|
||||
}).toThrow(/Invalid knowledge graph/);
|
||||
});
|
||||
|
||||
it("should skip validation when validate option is false", () => {
|
||||
const invalidGraph = { ...sampleGraph, version: 123 };
|
||||
saveGraph(tempDir, invalidGraph as unknown as KnowledgeGraph);
|
||||
|
||||
const loaded = loadGraph(tempDir, { validate: false });
|
||||
expect(loaded).not.toBeNull();
|
||||
expect(loaded?.version).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe("saveMeta / loadMeta", () => {
|
||||
|
||||
@@ -142,6 +142,31 @@ export default class AppController {}
|
||||
expect(exportNames).toContain("default");
|
||||
});
|
||||
|
||||
it("should handle export with aliases", () => {
|
||||
const code = `
|
||||
const originalName = () => true;
|
||||
export { originalName as renamedExport };
|
||||
`;
|
||||
const result = plugin.analyzeFile("test.ts", code);
|
||||
|
||||
const exportNames = result.exports.map((e) => e.name);
|
||||
expect(exportNames).toContain("renamedExport");
|
||||
});
|
||||
|
||||
it("should handle arrow functions without parameters", () => {
|
||||
const code = `
|
||||
const noParams = () => { return 42; };
|
||||
const withReturn = () => "hello";
|
||||
`;
|
||||
const result = plugin.analyzeFile("test.ts", code);
|
||||
|
||||
expect(result.functions).toHaveLength(2);
|
||||
expect(result.functions[0].name).toBe("noParams");
|
||||
expect(result.functions[0].params).toEqual([]);
|
||||
expect(result.functions[1].name).toBe("withReturn");
|
||||
expect(result.functions[1].params).toEqual([]);
|
||||
});
|
||||
|
||||
it("should extract functions from JavaScript files", () => {
|
||||
const code = `
|
||||
function hello() {
|
||||
|
||||
+3269
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user