"""Integration tests for #include completion and import completion in clice.""" import asyncio import pytest from lsprotocol.types import ( HoverParams, Position, TextDocumentIdentifier, ) from tests.integration.utils import doc from tests.integration.utils.workspace import did_change @pytest.mark.workspace("include_completion") async def test_include_completion_quoted(client, workspace): """Completion after #include " should list local headers.""" uri, _ = await client.open_and_wait(workspace / "main.cpp") # Update content to trigger include completion for "my" prefix. did_change(client, uri, 1, '#include "my') result = await client.completion_at(uri, 0, 12) # After "my" assert result is not None items = result.items if hasattr(result, "items") else result labels = [item.label for item in items] assert "myheader.h" in labels client.close(uri) @pytest.mark.workspace("include_completion") async def test_include_completion_subdirectory(client, workspace): """Completion for #include "subdir/ should list files in subdir.""" uri, _ = await client.open_and_wait(workspace / "main.cpp") did_change(client, uri, 1, '#include "subdir/') result = await client.completion_at(uri, 0, 17) # After "subdir/" assert result is not None items = result.items if hasattr(result, "items") else result labels = [item.label for item in items] assert "nested.h" in labels client.close(uri) @pytest.mark.workspace("include_completion") async def test_include_completion_angle_bracket(client, workspace): """Completion after #include < should list system headers.""" uri, _ = await client.open_and_wait(workspace / "main.cpp") did_change(client, uri, 1, "#include 0, f"Expected cstd* headers, got: {labels}" client.close(uri) @pytest.mark.workspace("include_completion") async def test_no_include_completion_on_regular_code(client, workspace): """Regular code should NOT trigger include completion (goes to worker).""" uri, _ = await client.open_and_wait(workspace / "main.cpp") did_change(client, uri, 1, "int x = ") result = await client.completion_at(uri, 0, 8) # Should return results from clang (keywords, etc.), not include paths. # Verify none of the results look like header filenames. assert result is not None items = result.items if hasattr(result, "items") else result labels = [item.label for item in items] assert "myheader.h" not in labels assert "nested.h" not in labels client.close(uri) @pytest.mark.workspace("include_completion") async def test_include_completion_empty_prefix(client, workspace): """Completion after #include " with no prefix should list all local headers.""" uri, _ = await client.open_and_wait(workspace / "main.cpp") did_change(client, uri, 1, '#include "') result = await client.completion_at(uri, 0, 10) # Right after the quote assert result is not None items = result.items if hasattr(result, "items") else result labels = [item.label for item in items] # With empty prefix, should list available headers including myheader.h # and the subdir/ directory entry. assert "myheader.h" in labels client.close(uri) @pytest.mark.workspace("modules/chained_modules") async def test_import_completion_basic(client, workspace): """Import completion should list known modules.""" # First open mod_a to ensure it's scanned and module A is registered. await client.open_and_wait(workspace / "mod_a.cppm") # Open mod_b and change its content to an incomplete import line. uri_b, _ = client.open(workspace / "mod_b.cppm") did_change(client, uri_b, 1, "import ") result = await client.completion_at(uri_b, 0, 7) assert result is not None items = result.items if hasattr(result, "items") else result labels = [item.label for item in items] assert "A" in labels, f"Expected 'A' in completion labels, got: {labels}" @pytest.mark.workspace("modules/chained_modules") async def test_import_completion_with_prefix(client, workspace): """Import completion with prefix should filter to matching modules.""" # Open mod_a to register module A. await client.open_and_wait(workspace / "mod_a.cppm") # Open mod_b and type 'import A' (with prefix). uri_b, _ = client.open(workspace / "mod_b.cppm") did_change(client, uri_b, 1, "import A") result = await client.completion_at(uri_b, 0, 8) assert result is not None items = result.items if hasattr(result, "items") else result labels = [item.label for item in items] assert "A" in labels, f"Expected 'A' in completion labels, got: {labels}" @pytest.mark.workspace("modules/dotted_module_name") async def test_import_completion_dotted_names(client, workspace): """Import completion should return dotted module names like my.app and my.io.""" # Open both module files to register them. await client.open_and_wait(workspace / "io.cppm") await client.open_and_wait(workspace / "app.cppm") # Change app.cppm to an incomplete import with dotted prefix. uri_app, _ = client.open(workspace / "app.cppm") did_change(client, uri_app, 1, "import my.") result = await client.completion_at(uri_app, 0, 10) assert result is not None items = result.items if hasattr(result, "items") else result labels = [item.label for item in items] assert "my.app" in labels or "my.io" in labels, ( f"Expected dotted module names in completion labels, got: {labels}" ) @pytest.mark.workspace("modules/consumer_imports_module") async def test_buffer_aware_module_deps(client, workspace): """Adding import in buffer (unsaved) should still build the needed PCM.""" # Open the module file first so it gets scanned. await client.open_and_wait(workspace / "math.cppm") # Open main.cpp with new content that imports Math (simulating unsaved edit). uri, _ = client.open(workspace / "main.cpp") did_change(client, uri, 1, "import Math;\nint x = add(1, 2);\n") # Trigger compilation via hover (pull-based model). event = client.wait_for_diagnostics(uri) await client.text_document_hover_async( HoverParams( text_document=TextDocumentIdentifier(uri=uri), position=Position(line=0, character=0), ) ) # Wait for diagnostics. await asyncio.wait_for(event.wait(), timeout=60.0) diags = client.diagnostics.get(uri, []) # Should have no errors if Math PCM was built successfully from buffer scan. errors = [d for d in diags if d.severity == 1] assert len(errors) == 0, f"Expected no errors, got: {errors}"