* 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>
315 lines
9.1 KiB
Python
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"})
|