Merge branch 'develop' into folder-journal-file-read-1692

This commit is contained in:
Micah Jerome Ellison 2023-03-06 11:19:52 -08:00
commit a21f3e19df
17 changed files with 175 additions and 140 deletions

View file

@ -25,13 +25,16 @@ Feature: Change entry times in journal
Scenario Outline: Change flag changes prompted entries
Given we use the config "<config_file>"
And we use the password "test" if prompted
When we run "jrnl -1"
Then the output should contain "2020-09-24 09:14 The third entry finally"
When we run "jrnl --short"
Then the output should be
2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing.
When we run "jrnl --change-time '2022-04-23 10:30'" and enter
Y
N
Y
When we run "jrnl -99 --short"
When we run "jrnl --short"
Then the output should be
2020-08-31 14:32 A second entry in what I hope to be a long series.
2022-04-23 10:30 Entry the first.

View file

@ -6,7 +6,9 @@ Feature: Installing jrnl
\n
\n
\n
Then the output should contain "Journal 'default' created"
Then the output should contain "jrnl configuration created at"
And the output should contain "For advanced features, read the docs at https://jrnl.sh"
And the output should contain "Journal 'default' created"
And the default journal "journal.txt" should be in the "." directory
And the config should contain "encrypt: false"
And the version in the config file should be up-to-date

View file

@ -125,7 +125,7 @@ Feature: Searching in a journal
| basic_dayone.yaml |
Scenario: Searching for unstarred entries
Scenario Outline: Searching for unstarred entries
Given we use the config "<config_file>"
And we use the password "test" if prompted
When we run "jrnl -not -starred"
@ -138,7 +138,7 @@ Feature: Searching in a journal
| basic_folder.yaml |
| basic_dayone.yaml |
Scenario: Searching for tagged entries
Scenario Outline: Searching for tagged entries
Given we use the config "<config_file>"
And we use the password "test" if prompted
When we run "jrnl -tagged"
@ -151,7 +151,7 @@ Feature: Searching in a journal
| basic_folder.yaml |
| basic_dayone.yaml |
Scenario: Searching for untagged entries
Scenario Outline: Searching for untagged entries
Given we use the config "empty_folder.yaml"
When we run "jrnl Tagged entry. This one has a @tag."
Then we should get no error

View file

@ -201,6 +201,16 @@ def input_method():
return ""
@fixture
def all_input():
return ""
@fixture
def command():
return ""
@fixture
def cache_dir():
return {"exists": False, "path": ""}
@ -221,13 +231,15 @@ def mock_user_input(request, password_input, stdin_input):
def _mock_user_input():
# user_input needs to be here because we don't know it until cli_run starts
user_input = get_fixture(request, "all_input", None)
if user_input is None:
user_input = Exception("Unexpected call for user input")
else:
user_input = iter(user_input.splitlines())
def mock_console_input(**kwargs):
if kwargs["password"] and not isinstance(password_input, Exception):
pw = kwargs.get("password", False)
if pw and not isinstance(password_input, Exception):
return password_input
if isinstance(user_input, Iterable):
@ -236,7 +248,7 @@ def mock_user_input(request, password_input, stdin_input):
return "" if input_line == r"\n" else input_line
# exceptions
return user_input if not kwargs["password"] else password_input
return user_input if not pw else password_input
mock_console = Mock(wraps=Console(stderr=True))
mock_console.input = Mock(side_effect=mock_console_input)

View file

@ -19,7 +19,6 @@ from jrnl.time import __get_pdt_calendar
from tests.lib.fixtures import FailedKeyring
from tests.lib.fixtures import NoKeyring
from tests.lib.fixtures import TestKeyring
from tests.lib.helpers import get_fixture
@given(parse("we {editor_method} to the editor if opened\n{editor_input}"))
@ -84,16 +83,16 @@ def we_have_type_of_keyring(keyring_type):
return TestKeyring()
@given(parse('we use the config "{config_file}"'), target_fixture="config_path")
@given(parse("we use no config"), target_fixture="config_path")
def we_use_the_config(request, temp_dir, working_dir):
config_file = get_fixture(request, "config_file")
def we_use_no_config(temp_dir):
os.chdir(temp_dir.name) # @todo move this step to a more universal place
return os.path.join(temp_dir.name, "non_existing_config.yaml")
@given(parse('we use the config "{config_file}"'), target_fixture="config_path")
def we_use_the_config(request, temp_dir, working_dir, config_file):
# Move into temp dir as cwd
os.chdir(temp_dir.name)
if not config_file:
return os.path.join(temp_dir.name, "non_existing_config.yaml")
os.chdir(temp_dir.name) # @todo move this step to a more universal place
# Copy the config file over
config_source = os.path.join(working_dir, "data", "configs", config_file)
@ -133,7 +132,7 @@ def config_exists(config_file, temp_dir, working_dir):
shutil.copy2(config_source, config_dest)
@given(parse('we use the password "{password}" if prompted'))
@given(parse('we use the password "{password}" if prompted'), target_fixture="password")
def use_password_forever(password):
return password

View file

@ -32,17 +32,6 @@ def does_directory_contain_n_files(directory_path, number):
return int(number) == count
def parse_should_or_should_not(should_or_should_not):
if should_or_should_not == "should":
return True
elif should_or_should_not == "should not":
return False
else:
raise Exception(
"should_or_should_not valid values are 'should' or 'should not'"
)
def assert_equal_tags_ignoring_order(
actual_line, expected_line, actual_content, expected_content
):
@ -81,7 +70,7 @@ def spy_wrapper(wrapped_function):
def get_fixture(request, name, default=None):
result = default
if name in request.node.fixturenames:
result = request.getfixturevalue(name)
return result
try:
return request.getfixturevalue(name)
except LookupError:
return default

View file

@ -15,7 +15,9 @@ from tests.lib.helpers import assert_equal_tags_ignoring_order
from tests.lib.helpers import does_directory_contain_files
from tests.lib.helpers import does_directory_contain_n_files
from tests.lib.helpers import get_nested_val
from tests.lib.helpers import parse_should_or_should_not
from tests.lib.type_builders import should_choice
SHOULD_DICT = {"Should": should_choice}
@then("we should get no error")
@ -31,40 +33,38 @@ def output_should_match(regex, cli_run):
assert matches, f"\nRegex didn't match:\n{regex}\n{str(out)}\n{str(matches)}"
@then(parse("the output {should_or_should_not} contain\n{expected_output}"))
@then(parse('the output {should_or_should_not} contain "{expected_output}"'))
@then(parse("the output {it_should:Should} contain\n{expected_output}", SHOULD_DICT))
@then(parse('the output {it_should:Should} contain "{expected_output}"', SHOULD_DICT))
@then(
parse(
"the {which_output_stream} output {should_or_should_not} contain\n{expected_output}"
"the {which_output_stream} output {it_should:Should} contain\n{expected_output}",
SHOULD_DICT,
)
)
@then(
parse(
'the {which_output_stream} output {should_or_should_not} contain "{expected_output}"'
'the {which_output_stream} output {it_should:Should} contain "{expected_output}"',
SHOULD_DICT,
)
)
def output_should_contain(
expected_output, which_output_stream, cli_run, should_or_should_not
):
we_should = parse_should_or_should_not(should_or_should_not)
def output_should_contain(expected_output, which_output_stream, cli_run, it_should):
output_str = f"\nEXPECTED:\n{expected_output}\n\nACTUAL STDOUT:\n{cli_run['stdout']}\n\nACTUAL STDERR:\n{cli_run['stderr']}"
assert expected_output
if which_output_stream is None:
assert ((expected_output in cli_run["stdout"]) == we_should) or (
(expected_output in cli_run["stderr"]) == we_should
assert ((expected_output in cli_run["stdout"]) == it_should) or (
(expected_output in cli_run["stderr"]) == it_should
), output_str
elif which_output_stream == "standard":
assert (expected_output in cli_run["stdout"]) == we_should, output_str
assert (expected_output in cli_run["stdout"]) == it_should, output_str
elif which_output_stream == "error":
assert (expected_output in cli_run["stderr"]) == we_should, output_str
assert (expected_output in cli_run["stderr"]) == it_should, output_str
else:
assert (
expected_output in cli_run[which_output_stream]
) == we_should, output_str
) == it_should, output_str
@then(parse("the output should not contain\n{expected_output}"))
@ -78,7 +78,7 @@ def output_should_not_contain(expected_output, cli_run):
def output_should_be(expected_output, cli_run):
actual = cli_run["stdout"].strip()
expected = expected_output.strip()
assert expected == actual
assert actual == expected
@then("the output should be empty")
@ -130,19 +130,19 @@ def default_journal_location(journal_file, journal_dir, config_on_disk, temp_dir
@then(
parse(
'the config for journal "{journal_name}" {should_or_should_not} contain "{some_yaml}"'
'the config for journal "{journal_name}" {it_should:Should} contain "{some_yaml}"',
SHOULD_DICT,
)
)
@then(
parse(
'the config for journal "{journal_name}" {should_or_should_not} contain\n{some_yaml}'
'the config for journal "{journal_name}" {it_should:Should} contain\n{some_yaml}',
SHOULD_DICT,
)
)
@then(parse('the config {should_or_should_not} contain "{some_yaml}"'))
@then(parse("the config {should_or_should_not} contain\n{some_yaml}"))
def config_var_on_disk(config_on_disk, journal_name, should_or_should_not, some_yaml):
we_should = parse_should_or_should_not(should_or_should_not)
@then(parse('the config {it_should:Should} contain "{some_yaml}"', SHOULD_DICT))
@then(parse("the config {it_should:Should} contain\n{some_yaml}", SHOULD_DICT))
def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):
actual = config_on_disk
if journal_name:
actual = actual["journals"][journal_name]
@ -154,26 +154,28 @@ def config_var_on_disk(config_on_disk, journal_name, should_or_should_not, some_
# `expected` objects formatted in yaml only compare one level deep
actual_slice = {key: actual.get(key, None) for key in expected.keys()}
assert (expected == actual_slice) == we_should
assert (expected == actual_slice) == it_should
@then(
parse(
'the config in memory for journal "{journal_name}" {should_or_should_not} contain "{some_yaml}"'
'the config in memory for journal "{journal_name}" {it_should:Should} contain "{some_yaml}"',
SHOULD_DICT,
)
)
@then(
parse(
'the config in memory for journal "{journal_name}" {should_or_should_not} contain\n{some_yaml}'
'the config in memory for journal "{journal_name}" {it_should:Should} contain\n{some_yaml}',
SHOULD_DICT,
)
)
@then(parse('the config in memory {should_or_should_not} contain "{some_yaml}"'))
@then(parse("the config in memory {should_or_should_not} contain\n{some_yaml}"))
def config_var_in_memory(
config_in_memory, journal_name, should_or_should_not, some_yaml
):
we_should = parse_should_or_should_not(should_or_should_not)
@then(
parse('the config in memory {it_should:Should} contain "{some_yaml}"', SHOULD_DICT)
)
@then(
parse("the config in memory {it_should:Should} contain\n{some_yaml}", SHOULD_DICT)
)
def config_var_in_memory(config_in_memory, journal_name, it_should, some_yaml):
actual = config_in_memory["overrides"]
if journal_name:
actual = actual["journals"][journal_name]
@ -185,7 +187,7 @@ def config_var_in_memory(
# `expected` objects formatted in yaml only compare one level deep
actual_slice = {key: get_nested_val(actual, key) for key in expected.keys()}
assert (expected == actual_slice) == we_should
assert (expected == actual_slice) == it_should
@then("we should be prompted for a password")
@ -224,31 +226,27 @@ def journal_directory_should_not_exist(config_on_disk, journal_name):
), f'Journal "{journal_name}" does exist'
@then(parse("the journal {should_or_should_not} exist"))
def journal_should_not_exist(config_on_disk, should_or_should_not):
@then(parse("the journal {it_should:Should} exist", SHOULD_DICT))
def journal_should_not_exist(config_on_disk, it_should):
scoped_config = scope_config(config_on_disk, "default")
expected_path = scoped_config["journal"]
contains_files = does_directory_contain_files(expected_path, ".")
if should_or_should_not == "should":
assert contains_files
elif should_or_should_not == "should not":
assert not contains_files
else:
raise Exception(
"should_or_should_not valid values are 'should' or 'should not'"
)
assert contains_files == it_should
@then(parse('the journal "{journal_name}" directory {should_or_should_not} exist'))
def directory_should_not_exist(config_on_disk, should_or_should_not, journal_name):
@then(
parse(
'the journal "{journal_name}" directory {it_should:Should} exist', SHOULD_DICT
)
)
def directory_should_not_exist(config_on_disk, it_should, journal_name):
scoped_config = scope_config(config_on_disk, journal_name)
expected_path = scoped_config["journal"]
we_should = parse_should_or_should_not(should_or_should_not)
dir_exists = os.path.isdir(expected_path)
assert dir_exists == we_should
assert dir_exists == it_should
@then(parse('the content of file "{file_path}" in the cache should be\n{file_content}'))
@ -383,26 +381,23 @@ def count_elements(number, item, cli_run):
assert len(xml_tree.findall(".//" + item)) == number
@then(parse("the editor {should_or_should_not} have been called"))
@then(parse("the editor {it_should:Should} have been called", SHOULD_DICT))
@then(
parse(
"the editor {should_or_should_not} have been called with {num_args} arguments"
"the editor {it_should:Should} have been called with {num_args} arguments",
SHOULD_DICT,
)
)
def count_editor_args(num_args, cli_run, editor_state, should_or_should_not):
we_should = parse_should_or_should_not(should_or_should_not)
assert cli_run["mocks"]["editor"].called == we_should
def count_editor_args(num_args, cli_run, editor_state, it_should):
assert cli_run["mocks"]["editor"].called == it_should
if isinstance(num_args, int):
assert len(editor_state["command"]) == int(num_args)
@then(parse("the stdin prompt {should_or_should_not} have been called"))
def stdin_prompt_called(cli_run, should_or_should_not):
we_should = parse_should_or_should_not(should_or_should_not)
assert cli_run["mocks"]["stdin_input"].called == we_should
@then(parse("the stdin prompt {it_should:Should} have been called", SHOULD_DICT))
def stdin_prompt_called(cli_run, it_should):
assert cli_run["mocks"]["stdin_input"].called == it_should
@then(parse('the editor filename should end with "{suffix}"'))

View file

@ -0,0 +1,11 @@
# Copyright © 2012-2023 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html
from parse_type import TypeBuilder
should_choice = TypeBuilder.make_enum(
{
"should": True,
"should not": False,
}
)

View file

@ -7,6 +7,7 @@ from contextlib import ExitStack
from pytest_bdd import when
from pytest_bdd.parsers import parse
from pytest_bdd.parsers import re
from pytest_bdd.steps import inject_fixture
from jrnl.main import run
@ -29,13 +30,20 @@ all_input = '("(?P<all_input>[^"]*)")'
@when(parse('we run "jrnl {command}" and {input_method}\n{all_input}'))
@when(re(f'we run "jrnl ?{command}" and {input_method} {all_input}'))
@when(parse('we run "jrnl {command}"'))
@when(re(f'we run "jrnl {command}"(?! and)'))
@when('we run "jrnl"')
def we_run_jrnl(cli_run, capsys, keyring):
def we_run_jrnl(capsys, keyring, request, command, input_method, all_input):
from keyring import set_keyring
set_keyring(keyring)
# fixture injection (pytest-bdd >=6.0)
inject_fixture(request, "command", command)
inject_fixture(request, "input_method", input_method)
inject_fixture(request, "all_input", all_input)
cli_run = request.getfixturevalue("cli_run")
with ExitStack() as stack:
mocks = cli_run["mocks"]
factories = cli_run["mock_factories"]