Improved auto-completion engine for formatting parameters and added support for absolute value in number formatting.
This commit is contained in:
@@ -431,14 +431,62 @@ def test_context_format_type_after_dot():
|
||||
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)
|
||||
def test_context_format_number_absolute_param_suggests_boolean():
|
||||
"""Test that 'absolute=' in format.number() triggers BOOLEAN_VALUE context."""
|
||||
text = "column amount:\n format.number(absolute="
|
||||
cursor = Position(line=1, ch=len(" format.number(absolute="))
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.FORMAT_PARAM_DATE
|
||||
assert context == Context.BOOLEAN_VALUE
|
||||
|
||||
|
||||
def test_context_format_type_while_typing():
|
||||
"""Test FORMAT_TYPE context while typing a format type name after 'format.'."""
|
||||
text = "column amount:\n format.dat"
|
||||
cursor = Position(line=1, ch=14)
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == Context.FORMAT_TYPE
|
||||
|
||||
|
||||
@pytest.mark.parametrize("line,expected_context", [
|
||||
(' format.number(', Context.FORMAT_PARAM_NUMBER),
|
||||
(' format.date(', Context.FORMAT_PARAM_DATE),
|
||||
(' format.boolean(', Context.FORMAT_PARAM_BOOLEAN),
|
||||
(' format.text(', Context.FORMAT_PARAM_TEXT),
|
||||
(' format.enum(', Context.FORMAT_PARAM_ENUM),
|
||||
(' format.constant(', Context.FORMAT_PARAM_CONSTANT),
|
||||
])
|
||||
def test_i_can_detect_format_param_context_after_open_paren(line, expected_context):
|
||||
"""Test FORMAT_PARAM_* context right after the opening parenthesis."""
|
||||
text = f"column amount:\n{line}"
|
||||
cursor = Position(line=1, ch=len(line))
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == expected_context
|
||||
|
||||
|
||||
@pytest.mark.parametrize("line,expected_context", [
|
||||
(' format.number(prefix="€ ",', Context.FORMAT_PARAM_NUMBER),
|
||||
(' format.date(format="%Y",', Context.FORMAT_PARAM_DATE),
|
||||
(' format.boolean(true_value="Yes",', Context.FORMAT_PARAM_BOOLEAN),
|
||||
(' format.text(transform="upper",', Context.FORMAT_PARAM_TEXT),
|
||||
(' format.enum(source="label",', Context.FORMAT_PARAM_ENUM),
|
||||
(' format.constant(value="N/A",', Context.FORMAT_PARAM_CONSTANT),
|
||||
])
|
||||
def test_i_can_detect_format_param_context_after_comma(line, expected_context):
|
||||
"""Test FORMAT_PARAM_* context after a comma (second parameter position)."""
|
||||
text = f"column amount:\n{line}"
|
||||
cursor = Position(line=1, ch=len(line))
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
context = detect_context(text, cursor, scope)
|
||||
assert context == expected_context
|
||||
|
||||
|
||||
|
||||
|
||||
def test_context_format_param_text():
|
||||
@@ -682,11 +730,12 @@ def test_suggestions_format_type(provider):
|
||||
suggestions = engine.get_suggestions(Context.FORMAT_TYPE, scope, "")
|
||||
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
|
||||
assert "number(" in labels
|
||||
assert "date(" in labels
|
||||
assert "boolean(" in labels
|
||||
assert "text(" in labels
|
||||
assert "enum(" in labels
|
||||
assert "constant(" in labels
|
||||
|
||||
|
||||
def test_suggestions_operators(provider):
|
||||
@@ -761,6 +810,62 @@ def test_suggestions_rule_start(provider):
|
||||
assert "format." in labels
|
||||
|
||||
|
||||
def test_suggestions_format_param_number(provider):
|
||||
"""Test suggestions for FORMAT_PARAM_NUMBER context."""
|
||||
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
suggestions = engine.get_suggestions(Context.FORMAT_PARAM_NUMBER, scope, "")
|
||||
labels = [s.label for s in suggestions]
|
||||
|
||||
assert "prefix=" in labels
|
||||
assert "suffix=" in labels
|
||||
assert "precision=" in labels
|
||||
assert "thousands_sep=" in labels
|
||||
assert "decimal_sep=" in labels
|
||||
assert "multiplier=" in labels
|
||||
assert "absolute=" in labels
|
||||
|
||||
|
||||
def test_suggestions_format_param_boolean(provider):
|
||||
"""Test suggestions for FORMAT_PARAM_BOOLEAN context."""
|
||||
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
suggestions = engine.get_suggestions(Context.FORMAT_PARAM_BOOLEAN, scope, "")
|
||||
labels = [s.label for s in suggestions]
|
||||
|
||||
assert "true_value=" in labels
|
||||
assert "false_value=" in labels
|
||||
assert "null_value=" in labels
|
||||
|
||||
|
||||
def test_suggestions_format_param_enum(provider):
|
||||
"""Test suggestions for FORMAT_PARAM_ENUM context."""
|
||||
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
suggestions = engine.get_suggestions(Context.FORMAT_PARAM_ENUM, scope, "")
|
||||
labels = [s.label for s in suggestions]
|
||||
|
||||
assert "source=" in labels
|
||||
assert "default=" in labels
|
||||
assert "allow_empty=" in labels
|
||||
assert "empty_label=" in labels
|
||||
assert "order_by=" in labels
|
||||
|
||||
|
||||
def test_suggestions_format_param_constant(provider):
|
||||
"""Test suggestions for FORMAT_PARAM_CONSTANT context."""
|
||||
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||
|
||||
suggestions = engine.get_suggestions(Context.FORMAT_PARAM_CONSTANT, scope, "")
|
||||
labels = [s.label for s in suggestions]
|
||||
|
||||
assert "value=" in labels
|
||||
|
||||
|
||||
def test_suggestions_none_context(provider):
|
||||
"""Test that NONE context returns empty suggestions."""
|
||||
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||
@@ -853,6 +958,64 @@ def test_i_can_get_completions_in_comment_returns_empty(provider):
|
||||
assert result.is_empty
|
||||
|
||||
|
||||
@pytest.mark.parametrize("text,cursor_line,used_param,remaining_param", [
|
||||
(
|
||||
'column amount:\n format.number(prefix="€ ",',
|
||||
1, "prefix=", "suffix=",
|
||||
),
|
||||
(
|
||||
'column amount:\n format.number(prefix="€ ", suffix=" CHF",',
|
||||
1, "suffix=", "precision=",
|
||||
),
|
||||
(
|
||||
'column amount:\n style(bold=True,',
|
||||
1, "bold=", "italic=",
|
||||
),
|
||||
(
|
||||
'column amount:\n style(background_color="red",',
|
||||
1, "background_color=", "color=",
|
||||
),
|
||||
])
|
||||
def test_i_cannot_get_already_used_param_in_suggestions(provider, text, cursor_line, used_param, remaining_param):
|
||||
"""Test that already-used parameters are not suggested again."""
|
||||
cursor = Position(line=cursor_line, ch=len(text.split("\n")[cursor_line]))
|
||||
|
||||
result = get_completions(text, cursor, provider, "app.orders")
|
||||
|
||||
labels = [s.label for s in result.suggestions]
|
||||
assert used_param not in labels, f"'{used_param}' should not appear after being used"
|
||||
assert remaining_param in labels, f"'{remaining_param}' should still appear"
|
||||
|
||||
|
||||
def test_i_can_get_completions_while_typing_param_name(provider):
|
||||
"""Test that suggestions appear while typing a partial parameter name.
|
||||
|
||||
Typing 'pre' after a comma should suggest 'precision=' (filtered by prefix)
|
||||
and exclude 'prefix=' (already used).
|
||||
"""
|
||||
text = 'column amount:\n format.number(prefix="€ ", pre'
|
||||
cursor = Position(line=1, ch=len(text.split("\n")[1]))
|
||||
|
||||
result = get_completions(text, cursor, provider, "app.orders")
|
||||
|
||||
labels = [s.label for s in result.suggestions]
|
||||
assert "precision=" in labels
|
||||
assert "prefix=" not in labels
|
||||
|
||||
|
||||
def test_i_can_get_completions_condition_unaffected_by_param_filtering(provider):
|
||||
"""Test that condition suggestions are not affected by parameter filtering."""
|
||||
text = 'column amount:\n style(bold=True) if '
|
||||
cursor = Position(line=1, ch=len(text.split("\n")[1]))
|
||||
|
||||
result = get_completions(text, cursor, provider, "app.orders")
|
||||
|
||||
labels = [s.label for s in result.suggestions]
|
||||
assert "value" in labels
|
||||
assert "col." in labels
|
||||
assert "not" in labels
|
||||
|
||||
|
||||
def test_i_can_create_formatting_completion_engine(provider):
|
||||
"""Test that FormattingCompletionEngine can be instantiated."""
|
||||
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||
@@ -882,3 +1045,64 @@ def test_i_can_use_engine_detect_context(provider):
|
||||
context = engine.detect_context(text, cursor, scope)
|
||||
|
||||
assert context == Context.STYLE_ARGS
|
||||
|
||||
|
||||
@pytest.mark.parametrize("format_type,expected_params", [
|
||||
("number", ["prefix=", "suffix=", "precision="]),
|
||||
("date", ["format="]),
|
||||
("boolean", ["true_value=", "false_value=", "null_value="]),
|
||||
("text", ["transform=", "max_length=", "ellipsis="]),
|
||||
("enum", ["source=", "default=", "allow_empty="]),
|
||||
("constant", ["value="]),
|
||||
])
|
||||
def test_i_can_get_completions_for_format_params(provider, format_type, expected_params):
|
||||
"""Test that correct parameters are suggested after 'format.<type>('."""
|
||||
text = f"column amount:\n format.{format_type}("
|
||||
cursor = Position(line=1, ch=len(f" format.{format_type}("))
|
||||
|
||||
result = get_completions(text, cursor, provider, "app.orders")
|
||||
|
||||
assert not result.is_empty
|
||||
labels = [s.label for s in result.suggestions]
|
||||
for param in expected_params:
|
||||
assert param in labels, f"Expected '{param}' in suggestions for format.{format_type}("
|
||||
|
||||
|
||||
def test_i_can_get_completions_for_format_type_after_dot(provider):
|
||||
"""Test that format types are suggested after 'format.'
|
||||
|
||||
The dot must be treated as a word delimiter so that the prefix
|
||||
is empty after the dot, allowing all FORMAT_TYPE suggestions through.
|
||||
Without this, the prefix 'format.' would filter out 'number(', 'date(', etc.
|
||||
"""
|
||||
text = "column amount:\n format."
|
||||
cursor = Position(line=1, ch=11)
|
||||
|
||||
result = get_completions(text, cursor, provider, "app.orders")
|
||||
|
||||
assert not result.is_empty
|
||||
labels = [s.label for s in result.suggestions]
|
||||
assert "number(" in labels
|
||||
assert "date(" in labels
|
||||
assert "boolean(" in labels
|
||||
assert "text(" in labels
|
||||
assert "enum(" in labels
|
||||
assert "constant(" in labels
|
||||
|
||||
|
||||
def test_i_can_get_completions_for_format_type_filtered_by_prefix(provider):
|
||||
"""Test that format types are filtered by partial input after 'format.'
|
||||
|
||||
Typing 'format.dat' should only suggest 'date(', not the other types.
|
||||
The dot delimiter ensures the prefix is 'dat', not 'format.dat'.
|
||||
"""
|
||||
text = "column amount:\n format.dat"
|
||||
cursor = Position(line=1, ch=14)
|
||||
|
||||
result = get_completions(text, cursor, provider, "app.orders")
|
||||
|
||||
labels = [s.label for s in result.suggestions]
|
||||
assert "date(" in labels
|
||||
assert "number(" not in labels
|
||||
assert "boolean(" not in labels
|
||||
assert "text(" not in labels
|
||||
|
||||
Reference in New Issue
Block a user