Files
puaros/packages/ipuaro/tests/unit/infrastructure/llm/prompts.test.ts
imfozilbek 36768c06d1 feat(ipuaro): add decorator extraction to initial context
Extract decorators from classes and methods for NestJS/Angular support.
Decorators are now shown in initial context:
- @Controller('users') class UserController
- @Get(':id') async getUser(id: string): Promise<User>

Changes:
- Add decorators field to FunctionInfo, MethodInfo, ClassInfo
- Update ASTParser to extract decorators from tree-sitter nodes
- Update formatFileSummary to display decorators prefix
- Add 18 unit tests for decorator extraction and formatting
2025-12-05 13:38:46 +05:00

2017 lines
70 KiB
TypeScript

import { describe, it, expect } from "vitest"
import {
SYSTEM_PROMPT,
buildInitialContext,
buildFileContext,
truncateContext,
type ProjectStructure,
} from "../../../../src/infrastructure/llm/prompts.js"
import type { FileAST } from "../../../../src/domain/value-objects/FileAST.js"
import type { FileMeta } from "../../../../src/domain/value-objects/FileMeta.js"
describe("prompts", () => {
describe("SYSTEM_PROMPT", () => {
it("should be a non-empty string", () => {
expect(typeof SYSTEM_PROMPT).toBe("string")
expect(SYSTEM_PROMPT.length).toBeGreaterThan(100)
})
it("should contain core principles", () => {
expect(SYSTEM_PROMPT).toContain("Lazy Loading")
expect(SYSTEM_PROMPT).toContain("Precision")
expect(SYSTEM_PROMPT).toContain("Safety")
})
it("should list available tools", () => {
expect(SYSTEM_PROMPT).toContain("get_lines")
expect(SYSTEM_PROMPT).toContain("edit_lines")
expect(SYSTEM_PROMPT).toContain("find_references")
expect(SYSTEM_PROMPT).toContain("git_status")
expect(SYSTEM_PROMPT).toContain("run_command")
})
it("should include safety rules", () => {
expect(SYSTEM_PROMPT).toContain("Safety Rules")
expect(SYSTEM_PROMPT).toContain("Never execute commands that could harm")
})
})
describe("buildInitialContext", () => {
const structure: ProjectStructure = {
name: "my-project",
rootPath: "/home/user/my-project",
files: ["src/index.ts", "src/utils.ts", "package.json"],
directories: ["src", "tests"],
}
const asts = new Map<string, FileAST>([
[
"src/index.ts",
{
imports: [],
exports: [],
functions: [
{
name: "main",
lineStart: 1,
lineEnd: 10,
params: [],
isAsync: false,
isExported: true,
},
],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
[
"src/utils.ts",
{
imports: [],
exports: [],
functions: [],
classes: [
{
name: "Helper",
lineStart: 1,
lineEnd: 20,
methods: [],
properties: [],
implements: [],
isExported: true,
isAbstract: false,
},
],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
it("should include project header", () => {
const context = buildInitialContext(structure, asts)
expect(context).toContain("# Project: my-project")
expect(context).toContain("Root: /home/user/my-project")
expect(context).toContain("Files: 3")
expect(context).toContain("Directories: 2")
})
it("should include directory structure", () => {
const context = buildInitialContext(structure, asts)
expect(context).toContain("## Structure")
expect(context).toContain("src/")
expect(context).toContain("tests/")
})
it("should include file overview with AST summaries (signatures format)", () => {
const context = buildInitialContext(structure, asts)
expect(context).toContain("## Files")
expect(context).toContain("### src/index.ts")
expect(context).toContain("- main()")
expect(context).toContain("### src/utils.ts")
expect(context).toContain("- class Helper")
})
it("should use compact format when includeSignatures is false", () => {
const context = buildInitialContext(structure, asts, undefined, {
includeSignatures: false,
})
expect(context).toContain("## Files")
expect(context).toContain("fn: main")
expect(context).toContain("class: Helper")
})
it("should include file flags from metadata", () => {
const metas = new Map<string, FileMeta>([
[
"src/index.ts",
{
complexity: { loc: 100, nesting: 3, cyclomaticComplexity: 10, score: 75 },
dependencies: [],
dependents: ["a.ts", "b.ts", "c.ts", "d.ts", "e.ts", "f.ts"],
isHub: true,
isEntryPoint: true,
fileType: "source",
},
],
])
const context = buildInitialContext(structure, asts, metas)
expect(context).toContain("(hub, entry, complex)")
})
})
describe("buildFileContext", () => {
const ast: FileAST = {
imports: [
{ name: "fs", from: "node:fs", line: 1, type: "builtin", isDefault: false },
{ name: "helper", from: "./helper", line: 2, type: "internal", isDefault: true },
],
exports: [
{ name: "main", line: 10, isDefault: false, kind: "function" },
{ name: "Config", line: 20, isDefault: true, kind: "class" },
],
functions: [
{
name: "main",
lineStart: 10,
lineEnd: 30,
params: [
{ name: "args", optional: false, hasDefault: false },
{ name: "options", optional: true, hasDefault: false },
],
isAsync: true,
isExported: true,
},
],
classes: [
{
name: "Config",
lineStart: 40,
lineEnd: 80,
methods: [
{
name: "load",
lineStart: 50,
lineEnd: 60,
params: [],
isAsync: false,
visibility: "public",
isStatic: false,
},
],
properties: [],
extends: "BaseConfig",
implements: ["IConfig"],
isExported: true,
isAbstract: false,
},
],
interfaces: [],
typeAliases: [],
parseError: false,
}
it("should include file path header", () => {
const context = buildFileContext("src/index.ts", ast)
expect(context).toContain("## src/index.ts")
})
it("should include imports section", () => {
const context = buildFileContext("src/index.ts", ast)
expect(context).toContain("### Imports")
expect(context).toContain('fs from "node:fs" (builtin)')
expect(context).toContain('helper from "./helper" (internal)')
})
it("should include exports section", () => {
const context = buildFileContext("src/index.ts", ast)
expect(context).toContain("### Exports")
expect(context).toContain("function main")
expect(context).toContain("class Config (default)")
})
it("should include functions section", () => {
const context = buildFileContext("src/index.ts", ast)
expect(context).toContain("### Functions")
expect(context).toContain("async main(args, options)")
expect(context).toContain("[10-30]")
})
it("should include classes section with methods", () => {
const context = buildFileContext("src/index.ts", ast)
expect(context).toContain("### Classes")
expect(context).toContain("Config extends BaseConfig implements IConfig")
expect(context).toContain("[40-80]")
expect(context).toContain("load()")
})
it("should include metadata section when provided", () => {
const meta: FileMeta = {
complexity: { loc: 100, nesting: 3, cyclomaticComplexity: 10, score: 65 },
dependencies: ["a.ts", "b.ts"],
dependents: ["c.ts"],
isHub: false,
isEntryPoint: true,
fileType: "source",
}
const context = buildFileContext("src/index.ts", ast, meta)
expect(context).toContain("### Metadata")
expect(context).toContain("LOC: 100")
expect(context).toContain("Complexity: 65/100")
expect(context).toContain("Dependencies: 2")
expect(context).toContain("Dependents: 1")
})
})
describe("buildFileContext - edge cases", () => {
it("should handle empty imports", () => {
const ast: FileAST = {
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
}
const context = buildFileContext("empty.ts", ast)
expect(context).toContain("## empty.ts")
expect(context).not.toContain("### Imports")
})
it("should handle empty exports", () => {
const ast: FileAST = {
imports: [{ name: "x", from: "./x", line: 1, type: "internal", isDefault: false }],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
}
const context = buildFileContext("no-exports.ts", ast)
expect(context).toContain("### Imports")
expect(context).not.toContain("### Exports")
})
it("should handle empty functions", () => {
const ast: FileAST = {
imports: [],
exports: [],
functions: [],
classes: [
{
name: "MyClass",
lineStart: 1,
lineEnd: 10,
methods: [],
properties: [],
implements: [],
isExported: false,
isAbstract: false,
},
],
interfaces: [],
typeAliases: [],
parseError: false,
}
const context = buildFileContext("no-functions.ts", ast)
expect(context).not.toContain("### Functions")
expect(context).toContain("### Classes")
})
it("should handle empty classes", () => {
const ast: FileAST = {
imports: [],
exports: [],
functions: [
{
name: "test",
lineStart: 1,
lineEnd: 5,
params: [],
isAsync: false,
isExported: false,
},
],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
}
const context = buildFileContext("no-classes.ts", ast)
expect(context).toContain("### Functions")
expect(context).not.toContain("### Classes")
})
it("should handle class without extends", () => {
const ast: FileAST = {
imports: [],
exports: [],
functions: [],
classes: [
{
name: "Standalone",
lineStart: 1,
lineEnd: 10,
methods: [],
properties: [],
implements: ["IFoo"],
isExported: false,
isAbstract: false,
},
],
interfaces: [],
typeAliases: [],
parseError: false,
}
const context = buildFileContext("standalone.ts", ast)
expect(context).toContain("Standalone implements IFoo")
expect(context).not.toContain("extends")
})
it("should handle class without implements", () => {
const ast: FileAST = {
imports: [],
exports: [],
functions: [],
classes: [
{
name: "Child",
lineStart: 1,
lineEnd: 10,
methods: [],
properties: [],
extends: "Parent",
implements: [],
isExported: false,
isAbstract: false,
},
],
interfaces: [],
typeAliases: [],
parseError: false,
}
const context = buildFileContext("child.ts", ast)
expect(context).toContain("Child extends Parent")
expect(context).not.toContain("implements")
})
it("should handle method with private visibility", () => {
const ast: FileAST = {
imports: [],
exports: [],
functions: [],
classes: [
{
name: "WithPrivate",
lineStart: 1,
lineEnd: 20,
methods: [
{
name: "secretMethod",
lineStart: 5,
lineEnd: 10,
params: [],
isAsync: false,
visibility: "private",
isStatic: false,
},
],
properties: [],
implements: [],
isExported: false,
isAbstract: false,
},
],
interfaces: [],
typeAliases: [],
parseError: false,
}
const context = buildFileContext("private.ts", ast)
expect(context).toContain("private secretMethod()")
})
it("should handle non-async function", () => {
const ast: FileAST = {
imports: [],
exports: [],
functions: [
{
name: "syncFn",
lineStart: 1,
lineEnd: 5,
params: [{ name: "x", optional: false, hasDefault: false }],
isAsync: false,
isExported: false,
},
],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
}
const context = buildFileContext("sync.ts", ast)
expect(context).toContain("syncFn(x)")
expect(context).not.toContain("async syncFn")
})
it("should handle export without default", () => {
const ast: FileAST = {
imports: [],
exports: [{ name: "foo", line: 1, isDefault: false, kind: "variable" }],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
}
const context = buildFileContext("named-export.ts", ast)
expect(context).toContain("variable foo")
expect(context).not.toContain("(default)")
})
})
describe("buildInitialContext - edge cases", () => {
it("should handle nested directory names", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: [],
directories: ["src/components/ui"],
}
const asts = new Map<string, FileAST>()
const context = buildInitialContext(structure, asts)
expect(context).toContain("ui/")
})
it("should handle file with only interfaces", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [
{
name: "IFoo",
lineStart: 1,
lineEnd: 5,
properties: [],
extends: [],
isExported: true,
},
],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- interface IFoo")
})
it("should handle file with only interfaces (compact format)", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [
{
name: "IFoo",
lineStart: 1,
lineEnd: 5,
properties: [],
extends: [],
isExported: true,
},
],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts, undefined, {
includeSignatures: false,
})
expect(context).toContain("interface: IFoo")
})
it("should handle file with only type aliases", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [{ name: "MyType", line: 1, isExported: true }],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- type MyType")
})
it("should handle file with only type aliases (compact format)", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [{ name: "MyType", line: 1, isExported: true }],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts, undefined, {
includeSignatures: false,
})
expect(context).toContain("type: MyType")
})
it("should handle file with no AST content", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["empty.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"empty.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- empty.ts")
})
it("should handle meta with only hub flag", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["hub.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"hub.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const metas = new Map<string, FileMeta>([
[
"hub.ts",
{
complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 },
dependencies: [],
dependents: [],
isHub: true,
isEntryPoint: false,
fileType: "source",
},
],
])
const context = buildInitialContext(structure, asts, metas)
expect(context).toContain("(hub)")
expect(context).not.toContain("entry")
expect(context).not.toContain("complex")
})
it("should handle meta with no flags", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["normal.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"normal.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const metas = new Map<string, FileMeta>([
[
"normal.ts",
{
complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 },
dependencies: [],
dependents: [],
isHub: false,
isEntryPoint: false,
fileType: "source",
},
],
])
const context = buildInitialContext(structure, asts, metas)
expect(context).toContain("- normal.ts")
expect(context).not.toContain("(hub")
expect(context).not.toContain("entry")
expect(context).not.toContain("complex")
})
it("should skip files not in AST map", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["exists.ts", "missing.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"exists.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("exists.ts")
expect(context).not.toContain("missing.ts")
})
it("should skip undefined AST entries", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["file.ts"],
directories: [],
}
const asts = new Map<string, FileAST>()
asts.set("file.ts", undefined as unknown as FileAST)
const context = buildInitialContext(structure, asts)
expect(context).toContain("## Files")
expect(context).not.toContain("file.ts")
})
})
describe("truncateContext", () => {
it("should return original context if within limit", () => {
const context = "Short context"
const result = truncateContext(context, 1000)
expect(result).toBe(context)
})
it("should truncate long context", () => {
const context = "a".repeat(1000)
const result = truncateContext(context, 100)
expect(result.length).toBeLessThan(500)
expect(result).toContain("truncated")
})
it("should break at newline boundary", () => {
const context = "Line 1\nLine 2\nLine 3\n" + "a".repeat(1000)
const result = truncateContext(context, 50)
expect(result).toContain("truncated")
})
})
describe("function signatures with types", () => {
it("should format function with typed parameters", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["user.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"user.ts",
{
imports: [],
exports: [],
functions: [
{
name: "getUser",
lineStart: 1,
lineEnd: 5,
params: [
{
name: "id",
type: "string",
optional: false,
hasDefault: false,
},
],
isAsync: false,
isExported: true,
},
],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- getUser(id: string)")
})
it("should format async function with return type", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["user.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"user.ts",
{
imports: [],
exports: [],
functions: [
{
name: "getUser",
lineStart: 1,
lineEnd: 5,
params: [
{
name: "id",
type: "string",
optional: false,
hasDefault: false,
},
],
isAsync: true,
isExported: true,
returnType: "Promise<User>",
},
],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- async getUser(id: string): Promise<User>")
})
it("should format function with optional parameters", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["utils.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"utils.ts",
{
imports: [],
exports: [],
functions: [
{
name: "format",
lineStart: 1,
lineEnd: 5,
params: [
{
name: "value",
type: "string",
optional: false,
hasDefault: false,
},
{
name: "options",
type: "FormatOptions",
optional: true,
hasDefault: false,
},
],
isAsync: false,
isExported: true,
returnType: "string",
},
],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- format(value: string, options?: FormatOptions): string")
})
it("should format function with multiple typed parameters", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["api.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"api.ts",
{
imports: [],
exports: [],
functions: [
{
name: "createUser",
lineStart: 1,
lineEnd: 10,
params: [
{
name: "name",
type: "string",
optional: false,
hasDefault: false,
},
{
name: "email",
type: "string",
optional: false,
hasDefault: false,
},
{
name: "age",
type: "number",
optional: true,
hasDefault: false,
},
],
isAsync: true,
isExported: true,
returnType: "Promise<User>",
},
],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain(
"- async createUser(name: string, email: string, age?: number): Promise<User>",
)
})
it("should format function without types (JavaScript style)", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["legacy.js"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"legacy.js",
{
imports: [],
exports: [],
functions: [
{
name: "doSomething",
lineStart: 1,
lineEnd: 5,
params: [
{ name: "x", optional: false, hasDefault: false },
{ name: "y", optional: false, hasDefault: false },
],
isAsync: false,
isExported: true,
},
],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- doSomething(x, y)")
})
it("should format interface with extends", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [
{
name: "AdminUser",
lineStart: 1,
lineEnd: 5,
properties: [],
extends: ["User", "Admin"],
isExported: true,
},
],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- interface AdminUser extends User, Admin")
})
})
describe("interface fields (0.24.2)", () => {
it("should format interface with fields", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["user.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"user.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [
{
name: "User",
lineStart: 1,
lineEnd: 5,
properties: [
{
name: "id",
line: 2,
type: "string",
visibility: "public",
isStatic: false,
isReadonly: false,
},
{
name: "name",
line: 3,
type: "string",
visibility: "public",
isStatic: false,
isReadonly: false,
},
{
name: "email",
line: 4,
type: "string",
visibility: "public",
isStatic: false,
isReadonly: false,
},
],
extends: [],
isExported: true,
},
],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("interface User { id: string, name: string, email: string }")
})
it("should format interface with extends and fields", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [
{
name: "AdminUser",
lineStart: 1,
lineEnd: 5,
properties: [
{
name: "role",
line: 2,
type: "string",
visibility: "public",
isStatic: false,
isReadonly: false,
},
],
extends: ["User"],
isExported: true,
},
],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("interface AdminUser extends User { role: string }")
})
it("should format interface with readonly fields", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [
{
name: "Config",
lineStart: 1,
lineEnd: 3,
properties: [
{
name: "version",
line: 2,
type: "string",
visibility: "public",
isStatic: false,
isReadonly: true,
},
],
extends: [],
isExported: true,
},
],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("interface Config { readonly version: string }")
})
it("should format interface with no type annotation", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [
{
name: "Loose",
lineStart: 1,
lineEnd: 3,
properties: [
{
name: "data",
line: 2,
visibility: "public",
isStatic: false,
isReadonly: false,
},
],
extends: [],
isExported: true,
},
],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("interface Loose { data }")
})
})
describe("type alias definitions (0.24.2)", () => {
it("should format type alias with definition", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [
{
name: "UserId",
line: 1,
isExported: true,
definition: "string",
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- type UserId = string")
})
it("should format union type alias", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [
{
name: "Status",
line: 1,
isExported: true,
definition: '"pending" | "active" | "done"',
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain('- type Status = "pending" | "active" | "done"')
})
it("should format intersection type alias", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [
{
name: "AdminUser",
line: 1,
isExported: true,
definition: "User & Admin",
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- type AdminUser = User & Admin")
})
it("should truncate long type definitions", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const longDefinition =
"{ id: string, name: string, email: string, phone: string, address: string, city: string, country: string, zip: string }"
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [
{
name: "BigType",
line: 1,
isExported: true,
definition: longDefinition,
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- type BigType = { id: string")
expect(context).toContain("...")
})
it("should format type alias without definition (fallback)", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [
{
name: "Unknown",
line: 1,
isExported: true,
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- type Unknown")
expect(context).not.toContain("- type Unknown =")
})
it("should format function type alias", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [
{
name: "Handler",
line: 1,
isExported: true,
definition: "(event: Event) => void",
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- type Handler = (event: Event) => void")
})
})
describe("enum definitions (0.24.3)", () => {
it("should format enum with numeric values", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["enums.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"enums.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
enums: [
{
name: "Status",
lineStart: 1,
lineEnd: 5,
members: [
{ name: "Active", value: 1 },
{ name: "Inactive", value: 0 },
{ name: "Pending", value: 2 },
],
isExported: true,
isConst: false,
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- enum Status { Active=1, Inactive=0, Pending=2 }")
})
it("should format enum with string values", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["enums.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"enums.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
enums: [
{
name: "Role",
lineStart: 1,
lineEnd: 5,
members: [
{ name: "Admin", value: "admin" },
{ name: "User", value: "user" },
],
isExported: true,
isConst: false,
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain('- enum Role { Admin="admin", User="user" }')
})
it("should format const enum", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["enums.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"enums.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
enums: [
{
name: "HttpStatus",
lineStart: 1,
lineEnd: 5,
members: [
{ name: "OK", value: 200 },
{ name: "NotFound", value: 404 },
],
isExported: true,
isConst: true,
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- const enum HttpStatus { OK=200, NotFound=404 }")
})
it("should format enum without explicit values", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["enums.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"enums.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
enums: [
{
name: "Direction",
lineStart: 1,
lineEnd: 5,
members: [
{ name: "Up", value: undefined },
{ name: "Down", value: undefined },
],
isExported: true,
isConst: false,
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- enum Direction { Up, Down }")
})
it("should format empty enum", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["enums.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"enums.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
enums: [
{
name: "Empty",
lineStart: 1,
lineEnd: 1,
members: [],
isExported: true,
isConst: false,
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- enum Empty")
})
it("should include enum in compact format", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["enums.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"enums.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
enums: [
{
name: "Status",
lineStart: 1,
lineEnd: 5,
members: [{ name: "Active", value: 1 }],
isExported: true,
isConst: false,
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts, undefined, {
includeSignatures: false,
})
expect(context).toContain("enum: Status")
})
it("should truncate long enum definitions", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["enums.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"enums.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [],
enums: [
{
name: "VeryLongEnumName",
lineStart: 1,
lineEnd: 20,
members: [
{ name: "FirstVeryLongMemberName", value: "first_value" },
{ name: "SecondVeryLongMemberName", value: "second_value" },
{ name: "ThirdVeryLongMemberName", value: "third_value" },
],
isExported: true,
isConst: false,
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("...")
expect(context).toContain("- enum VeryLongEnumName")
})
})
describe("decorators (0.24.4)", () => {
it("should format function with decorators", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["controller.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"controller.ts",
{
imports: [],
exports: [],
functions: [
{
name: "getUser",
lineStart: 1,
lineEnd: 5,
params: [
{
name: "id",
type: "string",
optional: false,
hasDefault: false,
},
],
isAsync: true,
isExported: true,
returnType: "Promise<User>",
decorators: ["@Get(':id')"],
},
],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- @Get(':id') async getUser(id: string): Promise<User>")
})
it("should format class with decorators", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["controller.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"controller.ts",
{
imports: [],
exports: [],
functions: [],
classes: [
{
name: "UserController",
lineStart: 1,
lineEnd: 20,
methods: [],
properties: [],
implements: [],
isExported: true,
isAbstract: false,
decorators: ["@Controller('users')"],
},
],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- @Controller('users') class UserController")
})
it("should format class with multiple decorators", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["service.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"service.ts",
{
imports: [],
exports: [],
functions: [],
classes: [
{
name: "UserService",
lineStart: 1,
lineEnd: 30,
methods: [],
properties: [],
implements: ["IUserService"],
isExported: true,
isAbstract: false,
decorators: ["@Injectable()", "@Singleton()"],
},
],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain(
"- @Injectable() @Singleton() class UserService implements IUserService",
)
})
it("should format function with multiple decorators", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["controller.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"controller.ts",
{
imports: [],
exports: [],
functions: [
{
name: "createUser",
lineStart: 1,
lineEnd: 10,
params: [],
isAsync: true,
isExported: true,
decorators: ["@Post()", "@Auth()", "@ValidateBody()"],
},
],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- @Post() @Auth() @ValidateBody() async createUser()")
})
it("should handle function without decorators", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["utils.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"utils.ts",
{
imports: [],
exports: [],
functions: [
{
name: "helper",
lineStart: 1,
lineEnd: 5,
params: [],
isAsync: false,
isExported: true,
},
],
classes: [],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- helper()")
expect(context).not.toContain("@")
})
it("should handle class without decorators", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["simple.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"simple.ts",
{
imports: [],
exports: [],
functions: [],
classes: [
{
name: "SimpleClass",
lineStart: 1,
lineEnd: 10,
methods: [],
properties: [],
implements: [],
isExported: true,
isAbstract: false,
},
],
interfaces: [],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- class SimpleClass")
expect(context).not.toContain("@")
})
})
})