Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

symbols.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

symbols.test.tsBlame138 lines · 1 contributor
4c8f666Claude1/**
2 * Block I8 — Symbol / xref navigation tests.
3 *
4 * Pure tests for the regex-based extractor per language + auth smoke on
5 * the reindex endpoint.
6 */
7
8import { describe, it, expect } from "bun:test";
9import app from "../app";
10import { detectLanguage, extractSymbols } from "../lib/symbols";
11
12describe("symbols — detectLanguage", () => {
13 it("maps common extensions", () => {
14 expect(detectLanguage("src/foo.ts")).toBe("ts");
15 expect(detectLanguage("src/Foo.tsx")).toBe("ts");
16 expect(detectLanguage("script.js")).toBe("ts");
17 expect(detectLanguage("app.py")).toBe("py");
18 expect(detectLanguage("main.rs")).toBe("rs");
19 expect(detectLanguage("main.go")).toBe("go");
20 expect(detectLanguage("App.java")).toBe("java");
21 expect(detectLanguage("Main.kt")).toBe("kt");
22 expect(detectLanguage("App.swift")).toBe("swift");
23 expect(detectLanguage("helper.rb")).toBe("rb");
24 });
25
26 it("returns null for unknown extensions", () => {
27 expect(detectLanguage("README.md")).toBe(null);
28 expect(detectLanguage("styles.css")).toBe(null);
29 expect(detectLanguage("noext")).toBe(null);
30 });
31});
32
33describe("symbols — extractSymbols (ts)", () => {
34 it("finds exported functions", () => {
35 const src = `export function foo() {}\nexport async function bar() {}`;
36 const syms = extractSymbols(src, "ts");
37 expect(syms.find((s) => s.name === "foo")?.kind).toBe("function");
38 expect(syms.find((s) => s.name === "bar")?.kind).toBe("function");
39 });
40
41 it("finds classes + interfaces + types", () => {
42 const src = [
43 "export class Widget {}",
44 "export interface Opts {}",
45 "export type ID = string;",
46 ].join("\n");
47 const syms = extractSymbols(src, "ts");
48 expect(syms.find((s) => s.name === "Widget")?.kind).toBe("class");
49 expect(syms.find((s) => s.name === "Opts")?.kind).toBe("interface");
50 expect(syms.find((s) => s.name === "ID")?.kind).toBe("type");
51 });
52
53 it("finds arrow-function consts as functions", () => {
54 const src = `export const handler = async (req) => {};`;
55 const syms = extractSymbols(src, "ts");
56 expect(syms.find((s) => s.name === "handler")?.kind).toBe("function");
57 });
58
59 it("records 1-based line numbers", () => {
60 const src = `// comment\nexport function foo() {}`;
61 const [sym] = extractSymbols(src, "ts");
62 expect(sym.line).toBe(2);
63 });
64
65 it("skips minified / overly long lines", () => {
66 const long = "x".repeat(600);
67 const src = `${long}\nexport function real() {}`;
68 const syms = extractSymbols(src, "ts");
69 expect(syms.length).toBe(1);
70 expect(syms[0].name).toBe("real");
71 });
72
73 it("truncates signature to 240 chars", () => {
74 // keep the line under 500 chars (extractor skips minified lines)
75 const src = `export function foo(${"x: string, ".repeat(30)}) {}`;
76 const [sym] = extractSymbols(src, "ts");
77 expect(sym.signature.length).toBeLessThanOrEqual(240);
78 });
79});
80
81describe("symbols — extractSymbols (python)", () => {
82 it("finds def and class", () => {
83 const src = `def load():\n pass\n\nclass Widget:\n pass`;
84 const syms = extractSymbols(src, "py");
85 expect(syms.find((s) => s.name === "load")?.kind).toBe("function");
86 expect(syms.find((s) => s.name === "Widget")?.kind).toBe("class");
87 });
88
89 it("finds SCREAMING_CASE constants", () => {
90 const src = `MAX_ITEMS = 100\nfoo = 1`;
91 const syms = extractSymbols(src, "py");
92 expect(syms.find((s) => s.name === "MAX_ITEMS")?.kind).toBe("const");
93 // lowercase should not match const rule
94 expect(syms.find((s) => s.name === "foo")).toBeUndefined();
95 });
96});
97
98describe("symbols — extractSymbols (rust + go)", () => {
99 it("finds rust fn/struct/trait", () => {
100 const src = [
101 "pub fn run() {}",
102 "pub struct Config {}",
103 "pub trait Loader {}",
104 ].join("\n");
105 const syms = extractSymbols(src, "rs");
106 expect(syms.find((s) => s.name === "run")?.kind).toBe("function");
107 expect(syms.find((s) => s.name === "Config")?.kind).toBe("class");
108 expect(syms.find((s) => s.name === "Loader")?.kind).toBe("interface");
109 });
110
111 it("finds go func + type struct + type interface", () => {
112 const src = [
113 "func Handle() {}",
114 "type Config struct {",
115 "type Loader interface {",
116 ].join("\n");
117 const syms = extractSymbols(src, "go");
118 expect(syms.find((s) => s.name === "Handle")?.kind).toBe("function");
119 expect(syms.find((s) => s.name === "Config")?.kind).toBe("class");
120 expect(syms.find((s) => s.name === "Loader")?.kind).toBe("interface");
121 });
122});
123
124describe("symbols — extractSymbols (unknown language)", () => {
125 it("returns empty for unknown language", () => {
126 expect(extractSymbols("blah", "unknown")).toEqual([]);
127 });
128});
129
130describe("symbols — route auth", () => {
131 it("POST /:owner/:repo/symbols/reindex without auth → 302 /login", async () => {
132 const res = await app.request("/alice/repo/symbols/reindex", {
133 method: "POST",
134 });
135 expect(res.status).toBe(302);
136 expect(res.headers.get("location") || "").toContain("/login");
137 });
138});