import os
import sys
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
pytestmark = pytest.mark.vue
IS_WINDOWS = sys.platform == "win32"
class TypeScriptServerBehavior:
"""Platform-specific TypeScript language server behavior for invalid positions.
On Windows: TS server returns empty results for invalid positions
On macOS/Linux: TS server raises exceptions with "Bad line number" or "Debug Failure"
"""
@staticmethod
def raises_on_invalid_position() -> bool:
return not IS_WINDOWS
@staticmethod
def returns_empty_on_invalid_position() -> bool:
return IS_WINDOWS
class TestVueInvalidPositions:
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_negative_line_number(self, language_server: SolidLanguageServer) -> None:
file_path = os.path.join("src", "components", "CalculatorInput.vue")
result = language_server.request_containing_symbol(file_path, -1, 0)
assert result is None or result == {}, f"Negative line number should return None or empty dict, got: {result}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_negative_character_number(self, language_server: SolidLanguageServer) -> None:
"""Test requesting containing symbol with negative character number.
Expected behavior: Should return None or empty dict, not crash.
"""
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Request containing symbol at invalid negative character
result = language_server.request_containing_symbol(file_path, 10, -1)
# Should handle gracefully - return None or empty dict
assert result is None or result == {}, f"Negative character number should return None or empty dict, got: {result}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_line_number_beyond_file_length(self, language_server: SolidLanguageServer) -> None:
"""Test requesting containing symbol beyond file length.
Expected behavior: Raises IndexError when trying to access line beyond file bounds.
This happens in the wrapper code before even reaching the language server.
"""
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Request containing symbol at line 99999 (way beyond file length)
# The wrapper code will raise an IndexError when checking if the line is empty
with pytest.raises(IndexError) as exc_info:
language_server.request_containing_symbol(file_path, 99999, 0)
# Verify it's an index error for list access
assert "list index out of range" in str(exc_info.value), f"Expected 'list index out of range' error, got: {exc_info.value}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_character_number_beyond_line_length(self, language_server: SolidLanguageServer) -> None:
"""Test requesting containing symbol beyond line length.
Expected behavior: Should return None or empty dict, not crash.
"""
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Request containing symbol at character 99999 (way beyond line length)
result = language_server.request_containing_symbol(file_path, 10, 99999)
# Should handle gracefully - return None or empty dict
assert result is None or result == {}, f"Character beyond line length should return None or empty dict, got: {result}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_references_at_negative_line(self, language_server: SolidLanguageServer) -> None:
"""Test requesting references with negative line number."""
from solidlsp.ls_exceptions import SolidLSPException
file_path = os.path.join("src", "components", "CalculatorInput.vue")
if TypeScriptServerBehavior.returns_empty_on_invalid_position():
result = language_server.request_references(file_path, -1, 0)
assert result == [], f"Expected empty list on Windows, got: {result}"
else:
with pytest.raises(SolidLSPException) as exc_info:
language_server.request_references(file_path, -1, 0)
assert "Bad line number" in str(exc_info.value) or "Debug Failure" in str(exc_info.value)
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_definition_at_invalid_position(self, language_server: SolidLanguageServer) -> None:
"""Test requesting definition at invalid position."""
from solidlsp.ls_exceptions import SolidLSPException
file_path = os.path.join("src", "components", "CalculatorInput.vue")
if TypeScriptServerBehavior.returns_empty_on_invalid_position():
result = language_server.request_definition(file_path, -1, 0)
assert result == [], f"Expected empty list on Windows, got: {result}"
else:
with pytest.raises(SolidLSPException) as exc_info:
language_server.request_definition(file_path, -1, 0)
assert "Bad line number" in str(exc_info.value) or "Debug Failure" in str(exc_info.value)
class TestVueNonExistentFiles:
"""Tests for handling non-existent files."""
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_document_symbols_nonexistent_file(self, language_server: SolidLanguageServer) -> None:
"""Test requesting document symbols from non-existent file.
Expected behavior: Should raise FileNotFoundError or return empty result.
"""
nonexistent_file = os.path.join("src", "components", "NonExistent.vue")
# Should raise an appropriate exception or return empty result
try:
result = language_server.request_document_symbols(nonexistent_file)
# If no exception, verify result is empty or indicates file not found
symbols = result.get_all_symbols_and_roots()
assert len(symbols[0]) == 0, f"Non-existent file should return empty symbols, got {len(symbols[0])} symbols"
except (FileNotFoundError, Exception) as e:
# Expected - file doesn't exist
assert True, f"Appropriately raised exception for non-existent file: {e}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_containing_symbol_nonexistent_file(self, language_server: SolidLanguageServer) -> None:
"""Test requesting containing symbol from non-existent file.
Expected behavior: Should raise FileNotFoundError or return None.
"""
nonexistent_file = os.path.join("src", "components", "NonExistent.vue")
# Should raise an appropriate exception or return None
try:
result = language_server.request_containing_symbol(nonexistent_file, 10, 10)
# If no exception, verify result indicates file not found
assert result is None or result == {}, f"Non-existent file should return None or empty dict, got: {result}"
except (FileNotFoundError, Exception) as e:
# Expected - file doesn't exist
assert True, f"Appropriately raised exception for non-existent file: {e}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_references_nonexistent_file(self, language_server: SolidLanguageServer) -> None:
"""Test requesting references from non-existent file.
Expected behavior: Should raise FileNotFoundError or return empty list.
"""
nonexistent_file = os.path.join("src", "components", "NonExistent.vue")
# Should raise an appropriate exception or return empty list
try:
result = language_server.request_references(nonexistent_file, 10, 10)
# If no exception, verify result is empty
assert result is None or isinstance(result, list), f"Non-existent file should return None or list, got: {result}"
if isinstance(result, list):
assert len(result) == 0, f"Non-existent file should return empty list, got {len(result)} references"
except (FileNotFoundError, Exception) as e:
# Expected - file doesn't exist
assert True, f"Appropriately raised exception for non-existent file: {e}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_definition_nonexistent_file(self, language_server: SolidLanguageServer) -> None:
"""Test requesting definition from non-existent file.
Expected behavior: Should raise FileNotFoundError or return empty list.
"""
nonexistent_file = os.path.join("src", "components", "NonExistent.vue")
# Should raise an appropriate exception or return empty list
try:
result = language_server.request_definition(nonexistent_file, 10, 10)
# If no exception, verify result is empty
assert isinstance(result, list), f"request_definition should return a list, got: {type(result)}"
assert len(result) == 0, f"Non-existent file should return empty list, got {len(result)} definitions"
except (FileNotFoundError, Exception) as e:
# Expected - file doesn't exist
assert True, f"Appropriately raised exception for non-existent file: {e}"
class TestVueUndefinedSymbols:
"""Tests for handling undefined or unreferenced symbols."""
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_references_for_unreferenced_symbol(self, language_server: SolidLanguageServer) -> None:
"""Test requesting references for a symbol that has no references.
Expected behavior: Should return empty list (only the definition itself if include_self=True).
"""
# Find a symbol that likely has no external references
file_path = os.path.join("src", "components", "CalculatorButton.vue")
# Get document symbols
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
# Find pressCount - this is exposed but may not be referenced elsewhere
press_count_symbol = next((s for s in symbols[0] if s.get("name") == "pressCount"), None)
if not press_count_symbol or "selectionRange" not in press_count_symbol:
pytest.skip("pressCount symbol not found - test fixture may need updating")
# Request references without include_self
sel_start = press_count_symbol["selectionRange"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
# Should return a list (may be empty or contain only definition)
assert isinstance(refs, list), f"request_references should return a list, got {type(refs)}"
# For an unreferenced symbol, should have 0-1 references (0 without include_self, 1 with)
# The exact count depends on the language server implementation
assert len(refs) <= 5, (
f"pressCount should have few or no external references. "
f"Got {len(refs)} references. This is not necessarily an error, just documenting behavior."
)
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_containing_symbol_at_whitespace_only_line(self, language_server: SolidLanguageServer) -> None:
"""Test requesting containing symbol at a whitespace-only line.
Expected behavior: Should return None, empty dict, or the parent symbol.
"""
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Try position at line 1 (typically a blank line or template start in Vue SFCs)
result = language_server.request_containing_symbol(file_path, 1, 0)
# Should handle gracefully - return None, empty dict, or a valid parent symbol
assert (
result is None or result == {} or isinstance(result, dict)
), f"Whitespace line should return None, empty dict, or valid symbol. Got: {result}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_definition_at_keyword_position(self, language_server: SolidLanguageServer) -> None:
"""Test requesting definition at language keyword position.
Expected behavior: Should return empty list or handle gracefully.
"""
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Try to get definition at a keyword like "const", "import", etc.
# Line 2 typically has "import" statement - try position on "import" keyword
result = language_server.request_definition(file_path, 2, 0)
# Should handle gracefully - return empty list or valid definitions
assert isinstance(result, list), f"request_definition should return a list, got {type(result)}"
class TestVueEdgeCasePositions:
"""Tests for edge case positions (0,0 and file boundaries)."""
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_containing_symbol_at_file_start(self, language_server: SolidLanguageServer) -> None:
"""Test requesting containing symbol at position (0,0).
Expected behavior: Should return None, empty dict, or a valid symbol.
This position typically corresponds to the start of the file (e.g., <template> tag).
"""
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Request containing symbol at position 0,0 (file start)
result = language_server.request_containing_symbol(file_path, 0, 0)
# Should handle gracefully
assert (
result is None or result == {} or isinstance(result, dict)
), f"Position 0,0 should return None, empty dict, or valid symbol. Got: {result}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_references_at_file_start(self, language_server: SolidLanguageServer) -> None:
"""Test requesting references at position (0,0).
Expected behavior: Should return None or empty list.
"""
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Request references at position 0,0 (file start)
result = language_server.request_references(file_path, 0, 0)
# Should handle gracefully
assert result is None or isinstance(result, list), f"Position 0,0 should return None or list. Got: {type(result)}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_definition_at_file_start(self, language_server: SolidLanguageServer) -> None:
"""Test requesting definition at position (0,0).
Expected behavior: Should return empty list.
"""
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Request definition at position 0,0 (file start)
result = language_server.request_definition(file_path, 0, 0)
# Should handle gracefully
assert isinstance(result, list), f"request_definition should return a list. Got: {type(result)}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_containing_symbol_in_template_section(self, language_server: SolidLanguageServer) -> None:
"""Test requesting containing symbol in the template section.
Expected behavior: Template positions typically have no containing symbol (return None or empty).
The Vue language server may not track template symbols the same way as script symbols.
"""
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Position likely in template section (early in file, before <script setup>)
# Exact line depends on file structure, but line 5-10 is often template
result = language_server.request_containing_symbol(file_path, 5, 10)
# Should handle gracefully - template doesn't have containing symbols in the same way
assert (
result is None or result == {} or isinstance(result, dict)
), f"Template position should return None, empty dict, or valid symbol. Got: {result}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_zero_character_positions(self, language_server: SolidLanguageServer) -> None:
"""Test requesting symbols at character position 0 (start of lines).
Expected behavior: Should handle gracefully, may or may not find symbols.
"""
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Test multiple lines at character 0
for line in [0, 10, 20, 30]:
result = language_server.request_containing_symbol(file_path, line, 0)
# Should handle gracefully
assert (
result is None or result == {} or isinstance(result, dict)
), f"Line {line}, character 0 should return None, empty dict, or valid symbol. Got: {result}"
class TestVueTypescriptFileErrors:
"""Tests for error handling in TypeScript files within Vue projects."""
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_typescript_file_invalid_position(self, language_server: SolidLanguageServer) -> None:
"""Test requesting symbols from TypeScript file at invalid position.
Expected behavior: Should handle gracefully.
"""
file_path = os.path.join("src", "stores", "calculator.ts")
# Request containing symbol at invalid position
result = language_server.request_containing_symbol(file_path, -1, -1)
# Should handle gracefully
assert result is None or result == {}, f"Invalid position in .ts file should return None or empty dict. Got: {result}"
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_typescript_file_beyond_bounds(self, language_server: SolidLanguageServer) -> None:
"""Test requesting symbols from TypeScript file beyond file bounds.
Expected behavior: Raises IndexError when trying to access line beyond file bounds.
"""
file_path = os.path.join("src", "stores", "calculator.ts")
# Request containing symbol beyond file bounds
# The wrapper code will raise an IndexError when checking if the line is empty
with pytest.raises(IndexError) as exc_info:
language_server.request_containing_symbol(file_path, 99999, 99999)
# Verify it's an index error for list access
assert "list index out of range" in str(exc_info.value), f"Expected 'list index out of range' error, got: {exc_info.value}"
class TestVueReferenceEdgeCases:
"""Tests for edge cases in reference finding."""
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_referencing_symbols_at_invalid_position(self, language_server: SolidLanguageServer) -> None:
"""Test requesting referencing symbols at invalid position."""
from solidlsp.ls_exceptions import SolidLSPException
file_path = os.path.join("src", "stores", "calculator.ts")
if TypeScriptServerBehavior.returns_empty_on_invalid_position():
result = list(language_server.request_referencing_symbols(file_path, -1, -1, include_self=False))
assert result == [], f"Expected empty list on Windows, got: {result}"
else:
with pytest.raises(SolidLSPException) as exc_info:
list(language_server.request_referencing_symbols(file_path, -1, -1, include_self=False))
assert "Bad line number" in str(exc_info.value) or "Debug Failure" in str(exc_info.value)
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_defining_symbol_at_invalid_position(self, language_server: SolidLanguageServer) -> None:
"""Test requesting defining symbol at invalid position."""
from solidlsp.ls_exceptions import SolidLSPException
file_path = os.path.join("src", "components", "CalculatorInput.vue")
if TypeScriptServerBehavior.returns_empty_on_invalid_position():
result = language_server.request_defining_symbol(file_path, -1, -1)
assert result is None, f"Expected None on Windows, got: {result}"
else:
with pytest.raises(SolidLSPException) as exc_info:
language_server.request_defining_symbol(file_path, -1, -1)
assert "Bad line number" in str(exc_info.value) or "Debug Failure" in str(exc_info.value)
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_referencing_symbols_beyond_file_bounds(self, language_server: SolidLanguageServer) -> None:
"""Test requesting referencing symbols beyond file bounds."""
from solidlsp.ls_exceptions import SolidLSPException
file_path = os.path.join("src", "stores", "calculator.ts")
if TypeScriptServerBehavior.returns_empty_on_invalid_position():
result = list(language_server.request_referencing_symbols(file_path, 99999, 99999, include_self=False))
assert result == [], f"Expected empty list on Windows, got: {result}"
else:
with pytest.raises(SolidLSPException) as exc_info:
list(language_server.request_referencing_symbols(file_path, 99999, 99999, include_self=False))
assert "Bad line number" in str(exc_info.value) or "Debug Failure" in str(exc_info.value)