feat(falyx): add persistent prompt history, multiline input support, and new enum tests

- Added `enable_prompt_history` & `prompt_history_base_dir` to Falyx, using FileHistory to persist inputs to `~/.{program}_history`
- Added `multiline` option to UserInputAction and passed through to PromptSession
- Updated examples (`argument_examples.py`, `confirm_example.py`) to enable prompt history and add TLDR examples
- Improved CLI usage tips for clarity (`[COMMAND]` instead of `[KEY]`)
- Added `test_action_types.py` and expanded `test_main.py` for init and parser coverage
- Bumped version to 0.1.76
This commit is contained in:
2025-07-27 14:00:51 -04:00
parent da38f6d6ee
commit 8a0a45e17f
8 changed files with 265 additions and 17 deletions

View File

@ -0,0 +1,145 @@
import pytest
from falyx.action.action_types import ConfirmType, FileType, SelectionReturnType
def test_file_type_enum():
"""Test if the FileType enum has all expected members."""
assert FileType.TEXT.value == "text"
assert FileType.PATH.value == "path"
assert FileType.JSON.value == "json"
assert FileType.TOML.value == "toml"
assert FileType.YAML.value == "yaml"
assert FileType.CSV.value == "csv"
assert FileType.TSV.value == "tsv"
assert FileType.XML.value == "xml"
assert str(FileType.TEXT) == "text"
def test_file_type_choices():
"""Test if the FileType choices method returns all enum members."""
choices = FileType.choices()
assert len(choices) == 8
assert all(isinstance(choice, FileType) for choice in choices)
def test_file_type_missing():
"""Test if the _missing_ method raises ValueError for invalid values."""
with pytest.raises(ValueError, match="Invalid FileType: 'invalid'"):
FileType._missing_("invalid")
with pytest.raises(ValueError, match="Invalid FileType: 123"):
FileType._missing_(123)
def test_file_type_aliases():
"""Test if the _get_alias method returns correct aliases."""
assert FileType._get_alias("file") == "path"
assert FileType._get_alias("filepath") == "path"
assert FileType._get_alias("unknown") == "unknown"
def test_file_type_missing_aliases():
"""Test if the _missing_ method handles aliases correctly."""
assert FileType._missing_("file") == FileType.PATH
assert FileType._missing_("filepath") == FileType.PATH
with pytest.raises(ValueError, match="Invalid FileType: 'unknown'"):
FileType._missing_("unknown")
def test_confirm_type_enum():
"""Test if the ConfirmType enum has all expected members."""
assert ConfirmType.YES_NO.value == "yes_no"
assert ConfirmType.YES_CANCEL.value == "yes_cancel"
assert ConfirmType.YES_NO_CANCEL.value == "yes_no_cancel"
assert ConfirmType.TYPE_WORD.value == "type_word"
assert ConfirmType.TYPE_WORD_CANCEL.value == "type_word_cancel"
assert ConfirmType.OK_CANCEL.value == "ok_cancel"
assert ConfirmType.ACKNOWLEDGE.value == "acknowledge"
assert str(ConfirmType.YES_NO) == "yes_no"
def test_confirm_type_choices():
"""Test if the ConfirmType choices method returns all enum members."""
choices = ConfirmType.choices()
assert len(choices) == 7
assert all(isinstance(choice, ConfirmType) for choice in choices)
def test_confirm_type_missing():
"""Test if the _missing_ method raises ValueError for invalid values."""
with pytest.raises(ValueError, match="Invalid ConfirmType: 'invalid'"):
ConfirmType._missing_("invalid")
with pytest.raises(ValueError, match="Invalid ConfirmType: 123"):
ConfirmType._missing_(123)
def test_confirm_type_aliases():
"""Test if the _get_alias method returns correct aliases."""
assert ConfirmType._get_alias("yes") == "yes_no"
assert ConfirmType._get_alias("ok") == "ok_cancel"
assert ConfirmType._get_alias("type") == "type_word"
assert ConfirmType._get_alias("word") == "type_word"
assert ConfirmType._get_alias("word_cancel") == "type_word_cancel"
assert ConfirmType._get_alias("ack") == "acknowledge"
def test_confirm_type_missing_aliases():
"""Test if the _missing_ method handles aliases correctly."""
assert ConfirmType("yes") == ConfirmType.YES_NO
assert ConfirmType("ok") == ConfirmType.OK_CANCEL
assert ConfirmType("word") == ConfirmType.TYPE_WORD
assert ConfirmType("ack") == ConfirmType.ACKNOWLEDGE
with pytest.raises(ValueError, match="Invalid ConfirmType: 'unknown'"):
ConfirmType._missing_("unknown")
def test_selection_return_type_enum():
"""Test if the SelectionReturnType enum has all expected members."""
assert SelectionReturnType.KEY.value == "key"
assert SelectionReturnType.VALUE.value == "value"
assert SelectionReturnType.DESCRIPTION.value == "description"
assert SelectionReturnType.DESCRIPTION_VALUE.value == "description_value"
assert SelectionReturnType.ITEMS.value == "items"
assert str(SelectionReturnType.KEY) == "key"
def test_selection_return_type_choices():
"""Test if the SelectionReturnType choices method returns all enum members."""
choices = SelectionReturnType.choices()
assert len(choices) == 5
assert all(isinstance(choice, SelectionReturnType) for choice in choices)
def test_selection_return_type_missing():
"""Test if the _missing_ method raises ValueError for invalid values."""
with pytest.raises(ValueError, match="Invalid SelectionReturnType: 'invalid'"):
SelectionReturnType._missing_("invalid")
with pytest.raises(ValueError, match="Invalid SelectionReturnType: 123"):
SelectionReturnType._missing_(123)
def test_selection_return_type_aliases():
"""Test if the _get_alias method returns correct aliases."""
assert SelectionReturnType._get_alias("desc") == "description"
assert SelectionReturnType._get_alias("desc_value") == "description_value"
assert SelectionReturnType._get_alias("unknown") == "unknown"
def test_selection_return_type_missing_aliases():
"""Test if the _missing_ method handles aliases correctly."""
assert SelectionReturnType._missing_("desc") == SelectionReturnType.DESCRIPTION
assert (
SelectionReturnType._missing_("desc_value")
== SelectionReturnType.DESCRIPTION_VALUE
)
with pytest.raises(ValueError, match="Invalid SelectionReturnType: 'unknown'"):
SelectionReturnType._missing_("unknown")

View File

@ -1,11 +1,40 @@
import os
import shutil
import sys
import tempfile
from argparse import ArgumentParser, Namespace, _SubParsersAction
from pathlib import Path
import pytest
from falyx.__main__ import bootstrap, find_falyx_config, main
from falyx.__main__ import (
bootstrap,
find_falyx_config,
get_parsers,
init_callback,
init_config,
main,
)
from falyx.parser import CommandArgumentParser
@pytest.fixture(autouse=True)
def fake_home(monkeypatch):
"""Redirect Path.home() to a temporary directory for all tests."""
temp_home = Path(tempfile.mkdtemp())
monkeypatch.setattr(Path, "home", lambda: temp_home)
yield temp_home
shutil.rmtree(temp_home, ignore_errors=True)
@pytest.fixture(autouse=True)
def setup_teardown():
"""Fixture to set up and tear down the environment for each test."""
cwd = Path.cwd()
yield
for file in cwd.glob("falyx.yaml"):
file.unlink(missing_ok=True)
for file in cwd.glob("falyx.toml"):
file.unlink(missing_ok=True)
def test_find_falyx_config():
@ -50,3 +79,54 @@ def test_bootstrap_with_global_config():
assert str(config_file.parent) in sys.path
config_file.unlink()
sys.path = sys_path_before
@pytest.mark.asyncio
async def test_init_config():
"""Test if the init_config function adds the correct argument."""
parser = CommandArgumentParser()
init_config(parser)
args = await parser.parse_args(["test_project"])
assert args["name"] == "test_project"
# Test with default value
args = await parser.parse_args([])
assert args["name"] == "."
def test_init_callback(tmp_path):
"""Test if the init_callback function works correctly."""
# Test project initialization
args = Namespace(command="init", name=str(tmp_path))
init_callback(args)
assert (tmp_path / "falyx.yaml").exists()
def test_init_global_callback():
# Test global initialization
args = Namespace(command="init_global")
init_callback(args)
assert (Path.home() / ".config" / "falyx" / "tasks.py").exists()
assert (Path.home() / ".config" / "falyx" / "falyx.yaml").exists()
def test_get_parsers():
"""Test if the get_parsers function returns the correct parsers."""
root_parser, subparsers = get_parsers()
assert isinstance(root_parser, ArgumentParser)
assert isinstance(subparsers, _SubParsersAction)
# Check if the 'init' command is available
init_parser = subparsers.choices.get("init")
assert init_parser is not None
assert "name" == init_parser._get_positional_actions()[0].dest
def test_main():
"""Test if the main function runs with the correct arguments."""
sys.argv = ["falyx", "run", "?"]
with pytest.raises(SystemExit) as exc_info:
main()
assert exc_info.value.code == 0