langflow/src/backend/tests/unit/inputs/test_inputs.py
Lucas Oliveira 95ceb52fa3
feat: add new Tab input (#7032)
* Added TabMixin

* Added Tab component on inputs

* Added Tab component to initializations

* Added tests for tab input

* Added Tab Component type

* Added options and active tab to input field type

* Added tab component on frontend

* Instantiate tab component

* Update package lock

* Refactor input classes and imports for consistency

- Reordered imports to maintain consistency across files.
- Simplified class definitions by removing unnecessary line breaks.
- Updated the `__all__` list in `__init__.py` files to include `TableInput` consistently.
- Adjusted test cases for cleaner syntax without altering functionality.

* Add constants for tab options limits in input mixin

- Introduced `MAX_TAB_OPTIONS` and `MAX_TAB_OPTION_LENGTH` constants for better maintainability.
- Updated validation logic in `TabMixin` to use these constants for clearer and more flexible error messages.

* Refactor tab input validation tests for improved clarity

- Replaced individual test cases for invalid tab inputs with a parameterized test function.
- Enhanced test coverage by including cases for too many options, exceeding character limits, and non-string values.
- Improved documentation within the test function for better understanding of validation scenarios.

* Enhance tab input validation tests with parameterization

- Refactored `test_tab_input_valid` to use `pytest.mark.parametrize` for improved test coverage and clarity.
- Included multiple scenarios for valid tab inputs, such as standard, fewer options, and empty options.
- Updated assertions to reflect the expected outcomes based on parameterized inputs.

* Enhance TabInput validation to ensure value is a string and one of the specified options

- Updated the `validate_value` method to enforce that the input value is a string.
- Added a check to validate that the value is among the allowed options, raising a ValueError with a descriptive message if not.
- Improved error handling for better user feedback on invalid inputs.

* Fix optional chaining in error handling within CodeAreaModal

- Updated the error check in the `delayedFunction` to use optional chaining for safer access to the error detail.
- This change ensures that the code handles cases where `detail` may be undefined, improving robustness.

* Add 'TAB' field type to schema and update direct types list

- Included 'TAB' as a valid field type in the schema conversion dictionary.
- Updated the DIRECT_TYPES list to include 'tab', ensuring consistency across type definitions.
- These changes enhance the flexibility of the input handling for tab components.

* Add unit test

* Re-added noqa

* fix: unit tests

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: italojohnny <italojohnnydosanjos@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-03-17 14:10:48 +00:00

315 lines
9.1 KiB
Python

import pytest
from langflow.inputs.inputs import (
BoolInput,
CodeInput,
DataInput,
DictInput,
DropdownInput,
FileInput,
FloatInput,
HandleInput,
InputTypesMap,
IntInput,
MessageTextInput,
MultilineInput,
MultilineSecretInput,
MultiselectInput,
NestedDictInput,
PromptInput,
SecretStrInput,
SliderInput,
StrInput,
TabInput,
TableInput,
)
from langflow.inputs.utils import instantiate_input
from langflow.schema.message import Message
from pydantic import ValidationError
def test_table_input_valid():
data = TableInput(name="valid_table", value=[{"key": "value"}, {"key2": "value2"}])
assert data.value == [{"key": "value"}, {"key2": "value2"}]
def test_slider_input_valid():
data = SliderInput(name="valid_slider", value=10)
assert data.value == 10
def test_table_input_invalid():
with pytest.raises(ValidationError):
TableInput(name="invalid_table", value="invalid")
with pytest.raises(ValidationError):
TableInput(name="invalid_table", value=[{"key": "value"}, "invalid"])
def test_str_input_valid():
data = StrInput(name="valid_str", value="This is a string")
assert data.value == "This is a string"
def test_str_input_invalid():
with pytest.warns(UserWarning):
StrInput(name="invalid_str", value=1234)
def test_message_text_input_valid():
data = MessageTextInput(name="valid_msg", value="This is a message")
assert data.value == "This is a message"
msg = Message(text="This is a message")
data = MessageTextInput(name="valid_msg", value=msg)
assert data.value == "This is a message"
def test_message_text_input_invalid():
with pytest.raises(ValidationError):
MessageTextInput(name="invalid_msg", value=1234)
def test_instantiate_input_valid():
data = {"name": "valid_input", "value": "This is a string"}
input_instance = instantiate_input("StrInput", data)
assert isinstance(input_instance, StrInput)
assert input_instance.value == "This is a string"
def test_instantiate_input_invalid():
with pytest.raises(ValueError, match="Invalid input type: InvalidInput"):
instantiate_input("InvalidInput", {"name": "invalid_input", "value": "This is a string"})
def test_handle_input_valid():
data = HandleInput(name="valid_handle", input_types=["BaseLanguageModel"])
assert data.input_types == ["BaseLanguageModel"]
def test_handle_input_invalid():
with pytest.raises(ValidationError):
HandleInput(name="invalid_handle", input_types="BaseLanguageModel")
def test_data_input_valid():
data_input = DataInput(name="valid_data", input_types=["Data"])
assert data_input.input_types == ["Data"]
def test_prompt_input_valid():
prompt_input = PromptInput(name="valid_prompt", value="Enter your name")
assert prompt_input.value == "Enter your name"
def test_code_input_valid():
code_input = CodeInput(name="valid_code", value="def hello():\n print('Hello, World!')")
assert code_input.value == "def hello():\n print('Hello, World!')"
def test_multiline_input_valid():
multiline_input = MultilineInput(name="valid_multiline", value="This is a\nmultiline input")
assert multiline_input.value == "This is a\nmultiline input"
assert multiline_input.multiline is True
def test_multiline_input_invalid():
with pytest.raises(ValidationError):
MultilineInput(name="invalid_multiline", value=1234)
def test_multiline_secret_input_valid():
multiline_secret_input = MultilineSecretInput(name="valid_multiline_secret", value="secret")
assert multiline_secret_input.value == "secret"
assert multiline_secret_input.password is True
def test_multiline_secret_input_invalid():
with pytest.raises(ValidationError):
MultilineSecretInput(name="invalid_multiline_secret", value=1234)
def test_secret_str_input_valid():
secret_str_input = SecretStrInput(name="valid_secret_str", value="supersecret")
assert secret_str_input.value == "supersecret"
assert secret_str_input.password is True
def test_secret_str_input_invalid():
with pytest.raises(ValidationError):
SecretStrInput(name="invalid_secret_str", value=1234)
def test_int_input_valid():
int_input = IntInput(name="valid_int", value=10)
assert int_input.value == 10
def test_int_input_invalid():
with pytest.raises(ValidationError):
IntInput(name="invalid_int", value="not_an_int")
def test_float_input_valid():
float_input = FloatInput(name="valid_float", value=10.5)
assert float_input.value == 10.5
def test_float_input_invalid():
with pytest.raises(ValidationError):
FloatInput(name="invalid_float", value="not_a_float")
def test_bool_input_valid():
bool_input = BoolInput(name="valid_bool", value=True)
assert bool_input.value is True
def test_bool_input_invalid():
with pytest.raises(ValidationError):
BoolInput(name="invalid_bool", value="not_a_bool")
def test_nested_dict_input_valid():
nested_dict_input = NestedDictInput(name="valid_nested_dict", value={"key": "value"})
assert nested_dict_input.value == {"key": "value"}
def test_nested_dict_input_invalid():
with pytest.raises(ValidationError):
NestedDictInput(name="invalid_nested_dict", value="not_a_dict")
def test_dict_input_valid():
dict_input = DictInput(name="valid_dict", value={"key": "value"})
assert dict_input.value == {"key": "value"}
def test_dict_input_invalid():
with pytest.raises(ValidationError):
DictInput(name="invalid_dict", value="not_a_dict")
def test_dropdown_input_valid():
dropdown_input = DropdownInput(name="valid_dropdown", options=["option1", "option2"])
assert dropdown_input.options == ["option1", "option2"]
def test_dropdown_input_invalid():
with pytest.raises(ValidationError):
DropdownInput(name="invalid_dropdown", options="option1")
def test_multiselect_input_valid():
multiselect_input = MultiselectInput(name="valid_multiselect", value=["option1", "option2"])
assert multiselect_input.value == ["option1", "option2"]
def test_multiselect_input_invalid():
with pytest.raises(ValidationError):
MultiselectInput(name="invalid_multiselect", value="option1")
def test_file_input_valid():
file_input = FileInput(name="valid_file", value=["/path/to/file"])
assert file_input.value == ["/path/to/file"]
@pytest.mark.parametrize(
("test_id", "options", "value", "expected_options", "expected_value"),
[
(
"standard_valid",
["Tab1", "Tab2", "Tab3"],
"Tab1",
["Tab1", "Tab2", "Tab3"],
"Tab1",
),
(
"fewer_options",
["Tab1", "Tab2"],
"Tab2",
["Tab1", "Tab2"],
"Tab2",
),
(
"empty_options",
[],
"",
[],
"",
),
],
)
def test_tab_input_valid(test_id, options, value, expected_options, expected_value):
"""Test TabInput validation with valid inputs."""
data = TabInput(
name=f"valid_tab_{test_id}",
options=options,
value=value,
)
assert data.options == expected_options
assert data.value == expected_value
@pytest.mark.parametrize(
("test_id", "options", "value", "error_expected"),
[
(
"too_many_options",
["Tab1", "Tab2", "Tab3", "Tab4"],
"Tab1",
ValidationError,
),
(
"option_too_long",
[
"Tab1",
"ThisTabValueIsTooLongAndExceedsTwentyCharacters",
"Tab3",
],
"Tab1",
ValidationError,
),
(
"non_string_value",
["Tab1", "Tab2", "Tab3"],
123,
TypeError,
),
],
)
def test_tab_input_invalid(test_id, options, value, error_expected):
"""Test TabInput validation with invalid inputs."""
if error_expected:
with pytest.raises(error_expected):
TabInput(
name=f"invalid_tab_{test_id}",
options=options,
value=value,
)
def test_instantiate_input_comprehensive():
valid_data = {
"StrInput": {"name": "str_input", "value": "A string"},
"IntInput": {"name": "int_input", "value": 10},
"FloatInput": {"name": "float_input", "value": 10.5},
"BoolInput": {"name": "bool_input", "value": True},
"DictInput": {"name": "dict_input", "value": {"key": "value"}},
"MultiselectInput": {
"name": "multiselect_input",
"value": ["option1", "option2"],
},
"TabInput": {
"name": "tab_input",
"options": ["Tab1", "Tab2", "Tab3"],
"value": "Tab1",
},
}
for input_type, data in valid_data.items():
input_instance = instantiate_input(input_type, data)
assert isinstance(input_instance, InputTypesMap[input_type])
with pytest.raises(ValueError, match="Invalid input type: InvalidInput"):
instantiate_input("InvalidInput", {"name": "invalid_input", "value": "Invalid"})