summaryrefslogtreecommitdiff
path: root/lua/99/test/completions_spec.lua
blob: 338571312e16c39d44050fa6f581b166d9bcfcfb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
-- luacheck: globals describe it assert before_each
---@diagnostic disable: undefined-field, missing-fields
local Completions = require("99.extensions.completions")
local eq = assert.are.same

local function mock_provider(trigger, name, valid_tokens)
  return {
    trigger = trigger,
    name = name,
    get_items = function()
      local items = {}
      for token, _ in pairs(valid_tokens) do
        table.insert(items, {
          label = token,
          insertText = trigger .. token,
          filterText = trigger .. token,
          kind = 1,
        })
      end
      return items
    end,
    is_valid = function(token)
      return valid_tokens[token] ~= nil
    end,
    resolve = function(token)
      return valid_tokens[token]
    end,
  }
end

describe("completions", function()
  before_each(function()
    Completions._reset()
  end)

  it("register and get_trigger_characters", function()
    Completions.register(mock_provider("#", "rules", {}))
    Completions.register(mock_provider("@", "files", {}))
    eq({ "#", "@" }, Completions.get_trigger_characters())
  end)

  it("register replaces provider with same trigger", function()
    Completions.register(
      mock_provider("#", "rules-v1", { old = "old-content" })
    )
    Completions.register(
      mock_provider("#", "rules-v2", { new = "new-content" })
    )

    local triggers = Completions.get_trigger_characters()
    eq({ "#" }, triggers)

    local refs = Completions.parse("use #new in prompt")
    eq(1, #refs)
    eq("new-content", refs[1].content)

    local old_refs = Completions.parse("use #old in prompt")
    eq(0, #old_refs)
  end)

  it("get_keyword_pattern builds pattern from triggers", function()
    Completions.register(mock_provider("#", "rules", {}))
    Completions.register(mock_provider("@", "files", {}))
    eq("[#@]\\k*", Completions.get_keyword_pattern())
  end)

  it("get_completions returns items for known trigger", function()
    Completions.register(mock_provider("#", "rules", { debug = "content" }))
    local items = Completions.get_completions("#")
    eq(1, #items)
    eq("debug", items[1].label)
    eq("#debug", items[1].insertText)
  end)

  it("get_completions returns empty for unknown trigger", function()
    Completions.register(mock_provider("#", "rules", {}))
    eq({}, Completions.get_completions("@"))
  end)

  it("parse extracts valid tokens and resolves content", function()
    Completions.register(mock_provider("#", "rules", {
      ["debug.md"] = "<debug>content</debug>",
    }))
    Completions.register(mock_provider("@", "files", {
      ["utils.lua"] = "```lua\ncode\n```",
    }))

    local refs = Completions.parse("add logging #debug.md and read @utils.lua")
    eq(2, #refs)
    eq("<debug>content</debug>", refs[1].content)
    eq("```lua\ncode\n```", refs[2].content)
  end)

  it("parse skips invalid tokens", function()
    Completions.register(mock_provider("#", "rules", {
      ["valid.md"] = "resolved",
    }))

    local refs = Completions.parse("#valid.md #nonexistent")
    eq(1, #refs)
    eq("resolved", refs[1].content)
  end)

  it("parse returns empty for no tokens", function()
    Completions.register(mock_provider("#", "rules", { a = "b" }))
    eq({}, Completions.parse("just a plain prompt"))
  end)

  it("real providers register and resolve through the registry", function()
    local Agents = require("99.extensions.agents")
    local Files = require("99.extensions.files")

    -- Set up files module
    local default_exclude = {
      ".env",
      ".env.*",
      "node_modules",
      ".git",
      "dist",
      "build",
      "*.log",
      ".DS_Store",
      "tmp",
      ".cursor",
    }
    Files.setup({ enabled = true, exclude = default_exclude }, {})
    Files.set_project_root(vim.uv.cwd() or "")
    Files.discover_files()

    -- Build a minimal state
    local state = {
      rules = Agents.rules({
        completion = {
          cursor_rules = "scratch/cursor/rules/",
          custom_rules = {},
        },
      }),
    }

    -- Register real providers through the registry
    Completions.register(Agents.completion_provider(state))
    Completions.register(Files.completion_provider())

    -- Verify triggers registered
    local triggers = Completions.get_trigger_characters()
    eq(2, #triggers)

    -- Parse a prompt with a real @file reference
    local refs = Completions.parse("check @scratch/refresh.lua")
    assert.is_true(#refs > 0, "expected at least one resolved reference")

    -- Verify resolved content is a real code fence with non-empty body
    assert.is_true(
      refs[1].content:sub(1, 6) == "```lua",
      "expected code fence from real file provider"
    )
    assert.is_true(#refs[1].content > 20, "expected non-trivial file content")

    -- Clean up
    Files.set_project_root("")
  end)
end)