Working on Formating DSL completion
This commit is contained in:
0
tests/core/formatting/dsl/__init__.py
Normal file
0
tests/core/formatting/dsl/__init__.py
Normal file
770
tests/core/formatting/dsl/test_completion.py
Normal file
770
tests/core/formatting/dsl/test_completion.py
Normal file
@@ -0,0 +1,770 @@
|
||||
"""
|
||||
Tests for formatting DSL autocompletion.
|
||||
|
||||
Tests for scope detection, context detection, suggestions, and engine integration.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from typing import Any
|
||||
|
||||
from myfasthtml.core.dsl.types import Position, Suggestion, CompletionResult
|
||||
from myfasthtml.core.formatting.dsl.completion.contexts import (
|
||||
Context,
|
||||
DetectedScope,
|
||||
detect_scope,
|
||||
detect_context,
|
||||
)
|
||||
from myfasthtml.core.formatting.dsl.completion.suggestions import get_suggestions
|
||||
from myfasthtml.core.formatting.dsl.completion.engine import (
|
||||
FormattingCompletionEngine,
|
||||
get_completions,
|
||||
)
|
||||
from myfasthtml.core.formatting.dsl.completion import presets
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Mock Provider Fixture
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class MockProvider:
|
||||
"""
|
||||
Mock metadata provider for testing.
|
||||
|
||||
Provides predefined data for columns, values, and presets.
|
||||
"""
|
||||
|
||||
def get_tables(self) -> list[str]:
|
||||
return ["app.orders"]
|
||||
|
||||
def get_columns(self, table: str) -> list[str]:
|
||||
return ["id", "amount", "status"]
|
||||
|
||||
def get_column_values(self, column: str) -> list[Any]:
|
||||
if column == "status":
|
||||
return ["draft", "pending", "approved"]
|
||||
if column == "amount":
|
||||
return [100, 250, 500]
|
||||
return []
|
||||
|
||||
def get_row_count(self, table: str) -> int:
|
||||
return 150
|
||||
|
||||
def get_style_presets(self) -> list[str]:
|
||||
return ["custom_highlight"]
|
||||
|
||||
def get_format_presets(self) -> list[str]:
|
||||
return ["CHF"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def provider():
|
||||
"""Return a mock provider for tests."""
|
||||
return MockProvider()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Scope Detection Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def test_i_can_detect_column_scope():
|
||||
"""Test detection of column scope."""
|
||||
text = "column amount:\n style()"
|
||||
scope = detect_scope(text, current_line=1)
|
||||
|
||||
assert scope.scope_type == "column"
|
||||
assert scope.column_name == "amount"
|
||||
assert scope.row_index is None
|
||||
|
||||
def test_i_can_detect_column_scope_after_first_line():
|
||||
"""Test detection of column scope."""
|
||||
text = "column amount:\n style()"
|
||||
scope = detect_scope(text, current_line=2)
|
||||
|
||||
assert scope.scope_type == "column"
|
||||
assert scope.column_name == "amount"
|
||||
assert scope.row_index is None
|
||||
|
||||
|
||||
def test_i_can_detect_column_scope_quoted():
|
||||
"""Test detection of column scope with quoted column name."""
|
||||
text = 'column "total amount":\n style()'
|
||||
scope = detect_scope(text, current_line=1)
|
||||
|
||||
assert scope.scope_type == "column"
|
||||
assert scope.column_name == "total amount"
|
||||
|
||||
|
||||
def test_i_can_detect_row_scope():
|
||||
"""Test detection of row scope."""
|
||||
text = "row 5:\n style()"
|
||||
scope = detect_scope(text, current_line=1)
|
||||
|
||||
assert scope.scope_type == "row"
|
||||
assert scope.row_index == 5
|
||||
assert scope.column_name is None
|
||||
|
||||
|
||||
def test_i_can_detect_cell_scope():
|
||||
"""Test detection of cell scope."""
|
||||
text = "cell (amount, 3):\n style()"
|
||||
scope = detect_scope(text, current_line=1)
|
||||
|
||||
assert scope.scope_type == "cell"
|
||||
assert scope.column_name == "amount"
|
||||
assert scope.row_index == 3
|
||||
|
||||
|
||||
def test_i_can_detect_cell_scope_quoted():
|
||||
"""Test detection of cell scope with quoted column name."""
|
||||
text = 'cell ("total amount", 3):\n style()'
|
||||
scope = detect_scope(text, current_line=1)
|
||||
|
||||
assert scope.scope_type == "cell"
|
||||
assert scope.column_name == "total amount"
|
||||
assert scope.row_index == 3
|
||||
|
||||
|
||||
def test_i_cannot_detect_scope_without_declaration():
|
||||
"""Test that no scope is detected when there's no declaration."""
|
||||
text = " style()"
|
||||
scope = detect_scope(text, current_line=0)
|
||||
|
||||
assert scope.scope_type is None
|
||||
|
||||
|
||||
def test_i_can_detect_scope_with_multiple_declarations():
|
||||
"""Test that the most recent scope is detected."""
|
||||
text = "column id:\n style()\ncolumn amount:\n format()"
|
||||
scope = detect_scope(text, current_line=3)
|
||||
|
||||
assert scope.scope_type == "column"
|
||||
assert scope.column_name == "amount"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Context Detection - Scope Contexts
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def test_context_scope_keyword_at_line_start():
|
||||
"""Test SCOPE_KEYWORD context at start of non-indented line."""
|
||||
text = ""
|
||||
cursor = Position(line=0, ch=0)
|
||||
scope = DetectedScope()
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.SCOPE_KEYWORD
|
||||
|
||||
|
||||
def test_context_scope_keyword_partial():
|
||||
"""Test SCOPE_KEYWORD context with partial keyword."""
|
||||
text = "col"
|
||||
cursor = Position(line=0, ch=3)
|
||||
scope = DetectedScope()
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.SCOPE_KEYWORD
|
||||
|
||||
|
||||
def test_context_column_name_after_column():
|
||||
"""Test COLUMN_NAME context after 'column '."""
|
||||
text = "column "
|
||||
cursor = Position(line=0, ch=7)
|
||||
scope = DetectedScope()
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.COLUMN_NAME
|
||||
|
||||
|
||||
def test_context_row_index_after_row():
|
||||
"""Test ROW_INDEX context after 'row '."""
|
||||
text = "row "
|
||||
cursor = Position(line=0, ch=4)
|
||||
scope = DetectedScope()
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.ROW_INDEX
|
||||
|
||||
|
||||
def test_context_cell_start_after_cell():
|
||||
"""Test CELL_START context after 'cell '."""
|
||||
text = "cell "
|
||||
cursor = Position(line=0, ch=5)
|
||||
scope = DetectedScope()
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.CELL_START
|
||||
|
||||
|
||||
def test_context_cell_column_after_open_paren():
|
||||
"""Test CELL_COLUMN context after 'cell ('."""
|
||||
text = "cell ("
|
||||
cursor = Position(line=0, ch=6)
|
||||
scope = DetectedScope()
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.CELL_COLUMN
|
||||
|
||||
|
||||
def test_context_cell_row_after_comma():
|
||||
"""Test CELL_ROW context after 'cell (amount, '."""
|
||||
text = "cell (amount, "
|
||||
cursor = Position(line=0, ch=14)
|
||||
scope = DetectedScope()
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.CELL_ROW
|
||||
|
||||
|
||||
def test_context_cell_row_after_comma_quoted():
|
||||
"""Test CELL_ROW context after 'cell ("column", '."""
|
||||
text = 'cell ("amount", '
|
||||
cursor = Position(line=0, ch=16)
|
||||
scope = DetectedScope()
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.CELL_ROW
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Context Detection - Rule Contexts
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def test_context_rule_start_on_indented_empty_line():
|
||||
"""Test RULE_START context on empty indented line."""
|
||||
text = "column amount:\n "
|
||||
cursor = Position(line=1, ch=4)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.RULE_START
|
||||
|
||||
|
||||
def test_context_rule_start_partial_keyword():
|
||||
"""Test RULE_START context with partial keyword."""
|
||||
text = "column amount:\n sty"
|
||||
cursor = Position(line=1, ch=7)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.RULE_START
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Context Detection - Style Contexts
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def test_context_style_args_after_open_paren():
|
||||
"""Test STYLE_ARGS context after 'style('."""
|
||||
text = "column amount:\n style("
|
||||
cursor = Position(line=1, ch=10)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.STYLE_ARGS
|
||||
|
||||
|
||||
def test_context_style_preset_inside_quotes():
|
||||
"""Test STYLE_PRESET context inside style quotes."""
|
||||
text = 'column amount:\n style("err'
|
||||
cursor = Position(line=1, ch=14)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.STYLE_PRESET
|
||||
|
||||
|
||||
def test_context_style_param_after_comma():
|
||||
"""Test STYLE_PARAM context after comma in style()."""
|
||||
text = 'column amount:\n style("error", '
|
||||
cursor = Position(line=1, ch=21)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.STYLE_PARAM
|
||||
|
||||
|
||||
def test_context_boolean_value_after_bold():
|
||||
"""Test BOOLEAN_VALUE context after 'bold='."""
|
||||
text = "column amount:\n style(bold="
|
||||
cursor = Position(line=1, ch=15)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.BOOLEAN_VALUE
|
||||
|
||||
|
||||
def test_context_boolean_value_after_italic():
|
||||
"""Test BOOLEAN_VALUE context after 'italic='."""
|
||||
text = "column amount:\n style(italic="
|
||||
cursor = Position(line=1, ch=17)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.BOOLEAN_VALUE
|
||||
|
||||
|
||||
def test_context_color_value_after_color():
|
||||
"""Test COLOR_VALUE context after 'color='."""
|
||||
text = "column amount:\n style(color="
|
||||
cursor = Position(line=1, ch=16)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.COLOR_VALUE
|
||||
|
||||
|
||||
def test_context_color_value_after_background_color():
|
||||
"""Test COLOR_VALUE context after 'background_color='."""
|
||||
text = "column amount:\n style(background_color="
|
||||
cursor = Position(line=1, ch=27)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.COLOR_VALUE
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Context Detection - Format Contexts
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def test_context_format_preset_inside_quotes():
|
||||
"""Test FORMAT_PRESET context inside format quotes."""
|
||||
text = 'column amount:\n format("EU'
|
||||
cursor = Position(line=1, ch=15)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.FORMAT_PRESET
|
||||
|
||||
|
||||
def test_context_format_type_after_dot():
|
||||
"""Test FORMAT_TYPE context after 'format.'."""
|
||||
text = "column amount:\n format."
|
||||
cursor = Position(line=1, ch=11)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.FORMAT_TYPE
|
||||
|
||||
|
||||
def test_context_format_param_date():
|
||||
"""Test FORMAT_PARAM_DATE context inside format.date()."""
|
||||
text = "column amount:\n format.date("
|
||||
cursor = Position(line=1, ch=16)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.FORMAT_PARAM_DATE
|
||||
|
||||
|
||||
def test_context_format_param_text():
|
||||
"""Test FORMAT_PARAM_TEXT context inside format.text()."""
|
||||
text = "column amount:\n format.text("
|
||||
cursor = Position(line=1, ch=16)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.FORMAT_PARAM_TEXT
|
||||
|
||||
|
||||
def test_context_date_format_value():
|
||||
"""Test DATE_FORMAT_VALUE context after 'format=' in format.date."""
|
||||
text = "column amount:\n format.date(format="
|
||||
cursor = Position(line=1, ch=23)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.DATE_FORMAT_VALUE
|
||||
|
||||
|
||||
def test_context_transform_value():
|
||||
"""Test TRANSFORM_VALUE context after 'transform='."""
|
||||
text = "column amount:\n format.text(transform="
|
||||
cursor = Position(line=1, ch=26)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.TRANSFORM_VALUE
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Context Detection - After Style/Format
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def test_context_after_style_or_format():
|
||||
"""Test AFTER_STYLE_OR_FORMAT context after closing paren."""
|
||||
text = 'column amount:\n style("error")'
|
||||
cursor = Position(line=1, ch=19)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.AFTER_STYLE_OR_FORMAT
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Context Detection - Condition Contexts
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def test_context_condition_start_after_if():
|
||||
"""Test CONDITION_START context after 'if '."""
|
||||
text = 'column amount:\n style("error") if '
|
||||
cursor = Position(line=1, ch=23)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.CONDITION_START
|
||||
|
||||
|
||||
def test_context_condition_after_not():
|
||||
"""Test CONDITION_AFTER_NOT context after 'if not '."""
|
||||
text = 'column amount:\n style("error") if not '
|
||||
cursor = Position(line=1, ch=27)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.CONDITION_AFTER_NOT
|
||||
|
||||
|
||||
def test_context_column_ref_after_col_dot():
|
||||
"""Test COLUMN_REF context after 'col.'."""
|
||||
text = 'column amount:\n style("error") if col.'
|
||||
cursor = Position(line=1, ch=28)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.COLUMN_REF
|
||||
|
||||
|
||||
def test_context_column_ref_quoted():
|
||||
"""Test COLUMN_REF_QUOTED context after 'col."'."""
|
||||
text = 'column amount:\n style("error") if col."'
|
||||
cursor = Position(line=1, ch=29)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.COLUMN_REF_QUOTED
|
||||
|
||||
|
||||
def test_context_operator_after_value():
|
||||
"""Test OPERATOR context after 'value '."""
|
||||
text = 'column amount:\n style("error") if value '
|
||||
cursor = Position(line=1, ch=30)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.OPERATOR
|
||||
|
||||
|
||||
def test_context_operator_value_after_equals():
|
||||
"""Test OPERATOR_VALUE context after 'value == '."""
|
||||
text = 'column amount:\n style("error") if value == '
|
||||
cursor = Position(line=1, ch=33)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.OPERATOR_VALUE
|
||||
|
||||
|
||||
def test_context_between_and():
|
||||
"""Test BETWEEN_AND context after 'between 0 '."""
|
||||
text = 'column amount:\n style("error") if value between 0 '
|
||||
cursor = Position(line=1, ch=39)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.BETWEEN_AND
|
||||
|
||||
|
||||
def test_context_between_value():
|
||||
"""Test BETWEEN_VALUE context after 'between 0 and '."""
|
||||
text = 'column amount:\n style("error") if value between 0 and '
|
||||
cursor = Position(line=1, ch=43)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.BETWEEN_VALUE
|
||||
|
||||
|
||||
def test_context_in_list_start():
|
||||
"""Test IN_LIST_START context after 'in '."""
|
||||
text = 'column status:\n style("error") if value in '
|
||||
cursor = Position(line=1, ch=33)
|
||||
scope = DetectedScope(scope_type="column", column_name="status")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.IN_LIST_START
|
||||
|
||||
|
||||
def test_context_in_list_value():
|
||||
"""Test IN_LIST_VALUE context after 'in ['."""
|
||||
text = 'column status:\n style("error") if value in ['
|
||||
cursor = Position(line=1, ch=34)
|
||||
scope = DetectedScope(scope_type="column", column_name="status")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.IN_LIST_VALUE
|
||||
|
||||
|
||||
def test_context_in_list_value_after_comma():
|
||||
"""Test IN_LIST_VALUE context after comma in list."""
|
||||
text = 'column status:\n style("error") if value in ["a", '
|
||||
cursor = Position(line=1, ch=39)
|
||||
scope = DetectedScope(scope_type="column", column_name="status")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.IN_LIST_VALUE
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Context Detection - Special Cases
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def test_context_none_in_comment():
|
||||
"""Test NONE context when cursor is in comment."""
|
||||
text = "column amount:\n # comment"
|
||||
cursor = Position(line=1, ch=15)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.NONE
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Suggestions Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def test_suggestions_scope_keyword(provider):
|
||||
"""Test suggestions for SCOPE_KEYWORD context."""
|
||||
scope = DetectedScope()
|
||||
|
||||
suggestions = get_suggestions(Context.SCOPE_KEYWORD, scope, provider)
|
||||
labels = [s.label for s in suggestions]
|
||||
|
||||
assert "column" in labels
|
||||
assert "row" in labels
|
||||
assert "cell" in labels
|
||||
|
||||
|
||||
def test_suggestions_style_preset(provider):
|
||||
"""Test suggestions for STYLE_PRESET context."""
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
suggestions = get_suggestions(Context.STYLE_PRESET, scope, provider)
|
||||
labels = [s.label for s in suggestions]
|
||||
|
||||
assert "primary" in labels
|
||||
assert "error" in labels
|
||||
assert "warning" in labels
|
||||
assert "custom_highlight" in labels # From provider
|
||||
|
||||
|
||||
def test_suggestions_format_type(provider):
|
||||
"""Test suggestions for FORMAT_TYPE context."""
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
suggestions = get_suggestions(Context.FORMAT_TYPE, scope, provider)
|
||||
labels = [s.label for s in suggestions]
|
||||
|
||||
assert "number" in labels
|
||||
assert "date" in labels
|
||||
assert "boolean" in labels
|
||||
assert "text" in labels
|
||||
assert "enum" in labels
|
||||
|
||||
|
||||
def test_suggestions_operators(provider):
|
||||
"""Test suggestions for OPERATOR context."""
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
suggestions = get_suggestions(Context.OPERATOR, scope, provider)
|
||||
labels = [s.label for s in suggestions]
|
||||
|
||||
assert "==" in labels
|
||||
assert "<" in labels
|
||||
assert "contains" in labels
|
||||
assert "in" in labels
|
||||
assert "between" in labels
|
||||
|
||||
|
||||
def test_suggestions_boolean_value(provider):
|
||||
"""Test suggestions for BOOLEAN_VALUE context."""
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
suggestions = get_suggestions(Context.BOOLEAN_VALUE, scope, provider)
|
||||
labels = [s.label for s in suggestions]
|
||||
|
||||
assert "True" in labels
|
||||
assert "False" in labels
|
||||
|
||||
|
||||
def test_suggestions_color_value(provider):
|
||||
"""Test suggestions for COLOR_VALUE context."""
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
suggestions = get_suggestions(Context.COLOR_VALUE, scope, provider)
|
||||
labels = [s.label for s in suggestions]
|
||||
|
||||
assert "red" in labels
|
||||
assert "blue" in labels
|
||||
assert "var(--color-primary)" in labels
|
||||
|
||||
|
||||
def test_suggestions_column_values(provider):
|
||||
"""Test suggestions for OPERATOR_VALUE context with column scope."""
|
||||
scope = DetectedScope(scope_type="column", column_name="status")
|
||||
|
||||
suggestions = get_suggestions(Context.OPERATOR_VALUE, scope, provider)
|
||||
labels = [s.label for s in suggestions]
|
||||
|
||||
# Base suggestions
|
||||
assert "col." in labels
|
||||
assert "True" in labels
|
||||
assert "False" in labels
|
||||
|
||||
# Column values from provider
|
||||
assert '"draft"' in labels
|
||||
assert '"pending"' in labels
|
||||
assert '"approved"' in labels
|
||||
|
||||
|
||||
def test_suggestions_rule_start(provider):
|
||||
"""Test suggestions for RULE_START context."""
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
suggestions = get_suggestions(Context.RULE_START, scope, provider)
|
||||
labels = [s.label for s in suggestions]
|
||||
|
||||
assert "style(" in labels
|
||||
assert "format(" in labels
|
||||
assert "format." in labels
|
||||
|
||||
|
||||
def test_suggestions_none_context(provider):
|
||||
"""Test that NONE context returns empty suggestions."""
|
||||
scope = DetectedScope()
|
||||
|
||||
suggestions = get_suggestions(Context.NONE, scope, provider)
|
||||
|
||||
assert suggestions == []
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Engine Integration Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def test_i_can_get_completions_for_style_preset(provider):
|
||||
"""Test complete flow for style preset completion."""
|
||||
text = 'column amount:\n style("'
|
||||
cursor = Position(line=1, ch=11)
|
||||
|
||||
result = get_completions(text, cursor, provider)
|
||||
|
||||
assert not result.is_empty
|
||||
labels = [s.label for s in result.suggestions]
|
||||
assert "primary" in labels
|
||||
assert "error" in labels
|
||||
|
||||
|
||||
def test_i_can_get_completions_filters_by_prefix(provider):
|
||||
"""Test that completions are filtered by prefix."""
|
||||
text = 'column amount:\n style("err'
|
||||
cursor = Position(line=1, ch=14)
|
||||
|
||||
result = get_completions(text, cursor, provider)
|
||||
|
||||
labels = [s.label for s in result.suggestions]
|
||||
assert "error" in labels
|
||||
assert "primary" not in labels
|
||||
|
||||
|
||||
def test_i_can_get_completions_returns_correct_positions(provider):
|
||||
"""Test that completion result has correct from/to positions."""
|
||||
text = 'column amount:\n style("err'
|
||||
cursor = Position(line=1, ch=14) # After "err"
|
||||
|
||||
result = get_completions(text, cursor, provider)
|
||||
|
||||
# from_pos should be at start of "err"
|
||||
assert result.from_pos.line == 1
|
||||
assert result.from_pos.ch == 11 # Start of "err"
|
||||
|
||||
# to_pos should be at end of "err"
|
||||
assert result.to_pos.line == 1
|
||||
assert result.to_pos.ch == 14 # End of "err"
|
||||
|
||||
|
||||
def test_i_can_get_completions_at_scope_start(provider):
|
||||
"""Test completions at the start of a new line (scope keywords)."""
|
||||
text = ""
|
||||
cursor = Position(line=0, ch=0)
|
||||
|
||||
result = get_completions(text, cursor, provider)
|
||||
|
||||
labels = [s.label for s in result.suggestions]
|
||||
assert "column" in labels
|
||||
assert "row" in labels
|
||||
assert "cell" in labels
|
||||
|
||||
|
||||
def test_i_can_get_completions_for_column_names(provider):
|
||||
"""Test completions for column names."""
|
||||
text = "column "
|
||||
cursor = Position(line=0, ch=7)
|
||||
|
||||
result = get_completions(text, cursor, provider)
|
||||
|
||||
labels = [s.label for s in result.suggestions]
|
||||
assert "id" in labels
|
||||
assert "amount" in labels
|
||||
assert "status" in labels
|
||||
|
||||
|
||||
def test_i_can_get_completions_in_comment_returns_empty(provider):
|
||||
"""Test that completions in comment are empty."""
|
||||
text = "column amount:\n # comment"
|
||||
cursor = Position(line=1, ch=15)
|
||||
|
||||
result = get_completions(text, cursor, provider)
|
||||
|
||||
assert result.is_empty
|
||||
|
||||
|
||||
def test_i_can_create_formatting_completion_engine(provider):
|
||||
"""Test that FormattingCompletionEngine can be instantiated."""
|
||||
engine = FormattingCompletionEngine(provider)
|
||||
|
||||
assert engine.provider == provider
|
||||
|
||||
|
||||
def test_i_can_use_engine_detect_scope(provider):
|
||||
"""Test engine's detect_scope method."""
|
||||
engine = FormattingCompletionEngine(provider)
|
||||
text = "column amount:\n style()"
|
||||
|
||||
scope = engine.detect_scope(text, current_line=1)
|
||||
|
||||
assert scope.scope_type == "column"
|
||||
assert scope.column_name == "amount"
|
||||
|
||||
|
||||
def test_i_can_use_engine_detect_context(provider):
|
||||
"""Test engine's detect_context method."""
|
||||
engine = FormattingCompletionEngine(provider)
|
||||
text = "column amount:\n style("
|
||||
cursor = Position(line=1, ch=10)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = engine.detect_context(text, cursor, scope)
|
||||
|
||||
assert context == Context.STYLE_ARGS
|
||||
Reference in New Issue
Block a user