diff --git a/lsp-server/test/test_provider/completion_test.cppm b/lsp-server/test/test_provider/completion_test.cppm index 8b6ff73..aa83b41 100644 --- a/lsp-server/test/test_provider/completion_test.cppm +++ b/lsp-server/test/test_provider/completion_test.cppm @@ -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).", TestClassMethodEmptyPrefixCompletion); runner.addTest("completion new Widget", TestNewCompletion); + runner.addTest("completion new ", TestNewEmptyPrefixCompletion); runner.addTest("completion new unit(MainUnit).Widget", TestUnitScopedNewCompletion); runner.addTest("completion createobject", TestCreateObjectCompletion); + runner.addTest("completion createobject ", TestCreateObjectEmptyPrefixCompletion); runner.addTest("completion createobject quoted", TestCreateObjectQuotedCompletion); + runner.addTest("completion createobject quoted ", 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()", 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()", 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()(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()(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; } }