test(lsp_server): cover completion trigger cases

Add explicit coverage for empty-prefix variants, unit/member dot access, createobject quoting forms, and resolve snippet placeholders.

Verified: test_provider (clang-linux-server/Release)
This commit is contained in:
csh 2025-12-24 12:23:53 +08:00
parent adec86a3aa
commit ec2c580a8c
1 changed files with 261 additions and 1 deletions

View File

@ -23,21 +23,34 @@ export namespace lsp::test::provider
private:
static TestResult TestClassMethodCompletion();
static TestResult TestClassMethodEmptyPrefixCompletion();
static TestResult TestNewCompletion();
static TestResult TestNewEmptyPrefixCompletion();
static TestResult TestUnitScopedNewCompletion();
static TestResult TestCreateObjectCompletion();
static TestResult TestCreateObjectEmptyPrefixCompletion();
static TestResult TestCreateObjectQuotedCompletion();
static TestResult TestCreateObjectQuotedEmptyPrefixCompletion();
static TestResult TestCreateObjectSingleQuoteCompletion();
static TestResult TestCreateObjectQualifiedCompletion();
static TestResult TestCreateObjectQuotedQualifiedCompletion();
static TestResult TestCreateObjectUnitCallQualifiedCompletion();
static TestResult TestUnitContextCompletion();
static TestResult TestUnitContextEmptyPrefixCompletion();
static TestResult TestUnitMemberCompletion();
static TestResult TestUnitMemberDotCompletion();
static TestResult TestObjectMemberCompletion();
static TestResult TestObjectMemberQualifiedSelfUnitCompletion();
static TestResult TestObjectMemberQualifiedTypeCompletion();
static TestResult TestFunctionCompletion();
static TestResult TestKeywordCompletion();
static TestResult TestClassContextCompletion();
static TestResult TestClassContextEmptyPrefixCompletion();
static TestResult TestUnitScopedNewAliasCompletion();
static TestResult TestCompletionResolveNewSnippet();
static TestResult TestCompletionResolveCreateObjectSnippet();
static TestResult TestCompletionResolveCreateObjectUnquotedSnippet();
static TestResult TestCompletionResolveCreateObjectSingleQuoteSnippet();
};
}
@ -150,21 +163,34 @@ namespace lsp::test::provider
void CompletionTests::Register(TestRunner& runner)
{
runner.addTest("completion class(Widget).", TestClassMethodCompletion);
runner.addTest("completion class(Widget).<empty>", TestClassMethodEmptyPrefixCompletion);
runner.addTest("completion new Widget", TestNewCompletion);
runner.addTest("completion new <empty>", TestNewEmptyPrefixCompletion);
runner.addTest("completion new unit(MainUnit).Widget", TestUnitScopedNewCompletion);
runner.addTest("completion createobject", TestCreateObjectCompletion);
runner.addTest("completion createobject <empty>", TestCreateObjectEmptyPrefixCompletion);
runner.addTest("completion createobject quoted", TestCreateObjectQuotedCompletion);
runner.addTest("completion createobject quoted <empty>", TestCreateObjectQuotedEmptyPrefixCompletion);
runner.addTest("completion createobject single quote", TestCreateObjectSingleQuoteCompletion);
runner.addTest("completion createobject qualified", TestCreateObjectQualifiedCompletion);
runner.addTest("completion createobject qualified quoted", TestCreateObjectQuotedQualifiedCompletion);
runner.addTest("completion createobject qualified unit()", TestCreateObjectUnitCallQualifiedCompletion);
runner.addTest("completion unit(", TestUnitContextCompletion);
runner.addTest("completion unit(<empty>)", TestUnitContextEmptyPrefixCompletion);
runner.addTest("completion unit member", TestUnitMemberCompletion);
runner.addTest("completion unit member dot", TestUnitMemberDotCompletion);
runner.addTest("completion object member", TestObjectMemberCompletion);
runner.addTest("completion object member qualified self unit", TestObjectMemberQualifiedSelfUnitCompletion);
runner.addTest("completion object member qualified type", TestObjectMemberQualifiedTypeCompletion);
runner.addTest("completion function prefix", TestFunctionCompletion);
runner.addTest("completion keyword prefix", TestKeywordCompletion);
runner.addTest("completion class(", TestClassContextCompletion);
runner.addTest("completion class(<empty>)", TestClassContextEmptyPrefixCompletion);
runner.addTest("completion new alias", TestUnitScopedNewAliasCompletion);
runner.addTest("completion resolve new snippet", TestCompletionResolveNewSnippet);
runner.addTest("completion resolve createobject snippet", TestCompletionResolveCreateObjectSnippet);
runner.addTest("completion resolve createobject unquoted snippet", TestCompletionResolveCreateObjectUnquotedSnippet);
runner.addTest("completion resolve createobject single quote snippet", TestCompletionResolveCreateObjectSingleQuoteSnippet);
}
TestResult CompletionTests::TestClassMethodCompletion()
@ -181,6 +207,20 @@ namespace lsp::test::provider
return result;
}
TestResult CompletionTests::TestClassMethodEmptyPrefixCompletion()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, "class(Widget).");
assertTrue(HasLabel(items, "StaticFoo"), "class() should suggest static methods with empty prefix");
return result;
}
TestResult CompletionTests::TestNewCompletion()
{
TestResult result{ "", true, "ok" };
@ -195,6 +235,20 @@ namespace lsp::test::provider
return result;
}
TestResult CompletionTests::TestNewEmptyPrefixCompletion()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, "obj := new ");
assertTrue(HasLabel(items, "Widget"), "new context should suggest classes with empty prefix");
return result;
}
TestResult CompletionTests::TestUnitScopedNewCompletion()
{
TestResult result{ "", true, "ok" };
@ -223,6 +277,20 @@ namespace lsp::test::provider
return result;
}
TestResult CompletionTests::TestCreateObjectEmptyPrefixCompletion()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, "aliased := createobject(");
assertTrue(HasLabel(items, "Widget"), "createobject should suggest classes with empty prefix");
return result;
}
TestResult CompletionTests::TestCreateObjectQuotedCompletion()
{
TestResult result{ "", true, "ok" };
@ -237,6 +305,39 @@ namespace lsp::test::provider
return result;
}
TestResult CompletionTests::TestCreateObjectQuotedEmptyPrefixCompletion()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, "aliased := createobject(\"");
assertTrue(HasLabel(items, "Widget"), "quoted createobject should suggest classes with empty prefix");
return result;
}
TestResult CompletionTests::TestCreateObjectSingleQuoteCompletion()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto replacement_pos = content.find("createobject(\"Wid\");");
assertTrue(replacement_pos != std::string::npos, "Fixture should include createobject(\"Wid\");");
content.replace(replacement_pos, std::string_view("createobject(\"Wid\");").size(), "createobject('Wid');");
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, "createobject('Wid");
assertTrue(HasLabel(items, "Widget"), "single-quote createobject should suggest classes");
return result;
}
TestResult CompletionTests::TestCreateObjectQualifiedCompletion()
{
TestResult result{ "", true, "ok" };
@ -251,6 +352,41 @@ namespace lsp::test::provider
return result;
}
TestResult CompletionTests::TestCreateObjectQuotedQualifiedCompletion()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, "createobject(\"MainUnit.");
assertTrue(HasLabel(items, "Widget"), "qualified quoted createobject should suggest unit classes");
return result;
}
TestResult CompletionTests::TestCreateObjectUnitCallQualifiedCompletion()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto replacement_pos = content.find("createobject(MainUnit.Wid);");
assertTrue(replacement_pos != std::string::npos, "Fixture should include createobject(MainUnit.Wid);");
content.replace(replacement_pos,
std::string_view("createobject(MainUnit.Wid);").size(),
"createobject(unit(MainUnit).Wid);");
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, "createobject(unit(MainUnit).Wid");
assertTrue(HasLabel(items, "Widget"), "unit() qualified createobject should suggest unit classes");
return result;
}
TestResult CompletionTests::TestUnitContextCompletion()
{
TestResult result{ "", true, "ok" };
@ -265,6 +401,20 @@ namespace lsp::test::provider
return result;
}
TestResult CompletionTests::TestUnitContextEmptyPrefixCompletion()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, " unit(");
assertTrue(HasLabel(items, "MainUnit"), "unit context should list visible units with empty prefix");
return result;
}
TestResult CompletionTests::TestUnitMemberCompletion()
{
TestResult result{ "", true, "ok" };
@ -279,6 +429,20 @@ namespace lsp::test::provider
return result;
}
TestResult CompletionTests::TestUnitMemberDotCompletion()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, "MainUnit.Uni");
assertTrue(HasLabel(items, "UnitFunc"), "unit member dot completion should list functions");
return result;
}
TestResult CompletionTests::TestObjectMemberCompletion()
{
TestResult result{ "", true, "ok" };
@ -293,6 +457,20 @@ namespace lsp::test::provider
return result;
}
TestResult CompletionTests::TestObjectMemberQualifiedSelfUnitCompletion()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, "aliased.Fo");
assertTrue(HasLabel(items, "Foo"), "qualified self unit type should resolve instance members");
return result;
}
TestResult CompletionTests::TestObjectMemberQualifiedTypeCompletion()
{
TestResult result{ "", true, "ok" };
@ -351,6 +529,21 @@ namespace lsp::test::provider
return result;
}
TestResult CompletionTests::TestClassContextEmptyPrefixCompletion()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, "class(");
assertTrue(HasLabel(items, "Widget"), "class context should suggest classes with static methods");
assertFalse(HasLabel(items, "Plain"), "class context should not suggest classes without static methods");
return result;
}
TestResult CompletionTests::TestUnitScopedNewAliasCompletion()
{
TestResult result{ "", true, "ok" };
@ -390,6 +583,7 @@ namespace lsp::test::provider
assertTrue(resolved.insertText.has_value(), "Resolved item should have insertText");
assertTrue(resolved.insertText.value().find("Widget(") == 0, "Resolved new snippet should start with Widget(");
assertTrue(resolved.insertText.value().contains("${1:a}"), "Resolved new snippet should include param placeholders");
return result;
}
@ -418,7 +612,73 @@ namespace lsp::test::provider
assertTrue(resolved.insertText.has_value(), "Resolved item should have insertText");
auto snippet = resolved.insertText.value();
assertTrue(!snippet.empty() && snippet.front() == 'W', "Resolved createobject snippet should not add quote");
assertTrue(snippet.starts_with("Widget\""), "Resolved createobject snippet should close quote but not add opening quote");
assertTrue(snippet.contains("${1:a}"), "Resolved createobject snippet should include param placeholders");
return result;
}
TestResult CompletionTests::TestCompletionResolveCreateObjectUnquotedSnippet()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, "createobject(Wid");
auto item = FindItem(items, "Widget");
assertTrue(item.has_value(), "Expected Widget completion");
protocol::RequestMessage request;
request.id = "r3";
request.method = "completionItem/resolve";
request.params = codec::ToLSPAny(*item);
::lsp::provider::completion_item::Resolve resolver;
auto json = resolver.ProvideResponse(request, env.context);
auto response = ParseResponse(json);
auto resolved = codec::FromLSPAny.template operator()<protocol::CompletionItem>(response.result.value());
assertTrue(resolved.insertText.has_value(), "Resolved item should have insertText");
auto snippet = resolved.insertText.value();
assertTrue(snippet.starts_with("\"Widget\""), "Resolved createobject snippet should add opening quote for unquoted call");
assertTrue(snippet.contains("${1:a}"), "Resolved createobject snippet should include param placeholders");
return result;
}
TestResult CompletionTests::TestCompletionResolveCreateObjectSingleQuoteSnippet()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("main_unit.tsf");
auto content = ReadTextFile(path);
auto replacement_pos = content.find("createobject(\"Wid\");");
assertTrue(replacement_pos != std::string::npos, "Fixture should include createobject(\"Wid\");");
content.replace(replacement_pos, std::string_view("createobject(\"Wid\");").size(), "createobject('Wid');");
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
auto items = RequestCompletion(env, uri, content, "createobject('Wid");
auto item = FindItem(items, "Widget");
assertTrue(item.has_value(), "Expected Widget completion");
protocol::RequestMessage request;
request.id = "r4";
request.method = "completionItem/resolve";
request.params = codec::ToLSPAny(*item);
::lsp::provider::completion_item::Resolve resolver;
auto json = resolver.ProvideResponse(request, env.context);
auto response = ParseResponse(json);
auto resolved = codec::FromLSPAny.template operator()<protocol::CompletionItem>(response.result.value());
assertTrue(resolved.insertText.has_value(), "Resolved item should have insertText");
auto snippet = resolved.insertText.value();
assertTrue(snippet.starts_with("Widget'"), "Resolved createobject snippet should close single quote but not add opening quote");
assertTrue(snippet.contains("${1:a}"), "Resolved createobject snippet should include param placeholders");
return result;
}
}