mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-06-27 21:16:14 +02:00
Rework how all output and messaging works in jrnl (#1475)
* fix missed statement from last PR * replace print statement for adding an entry to a journal * clean up linting and format * change print statement over to new print_msg function * make print_msg always print to stderr * change print statement over to new print_msg function * update importer to use new message function * update yaml format to use new message function * code cleanup * update yaml format to use new message function * update yaml format to use new exception handling * update Journal class to use new message function * update install module to use new message function * update config module to use new message function * update upgrade module to properly use new message and exception handling * fix typo * update upgrade module to use new message handling * update welcome message to use new handling * update upgrade module to use new message handling * update upgrade module journal summaries to use new message handling * take out old code * update upgrade module to use new message handling * update upgrade module to use new message handling * update more modules to use new message handling * take out old comment * update deprecated_cmd to use new message handling * update text_exporter with new message handling, get rid of old color constants * get rid of hardcoded text * whitespace changes * rework MsgType into MsgStyle so messages can have different styles * add comment * Move around code to separate concerns of each function a bit more * update create_password and yesno prompt functions for new messaging * fix missing newline for keyboard interrupts * fix misc linting * fix bug with panel titles always showing 'error' after one error * fix missing import * update debug output after uncaught exception * update exception for new exception handling * rewrite yesno function to use new centralized messages * reduce the debug output slightly * clean up print_msgs function * clean up create_password function * clean up misc linting * rename screen_input to hide_input to be more clear * update encrypted journal prompt to use new messaging functionality * fix typo in message key * move rich console into function so we can mock properly * update password mock to use rich console instead of getpass * add more helpful output to then step * fix test by updating expected output * update message to use new functionality * rework mocks in test suite for new messaging functionality * fix linting issue * fix more tests * fix more tests * fix more tests * fix more tests * fix merge bug * update prompt_action_entries to use new messaging functionality * Add new input_method "type" This does the same thing as input_method "pipe" but is more clear what it's doing (typing text into the builtin composer) * get rid of old commented code * get rid of unused code * move some files around Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>
This commit is contained in:
parent
4d683a13c0
commit
f53110c69b
38 changed files with 912 additions and 470 deletions
|
@ -73,7 +73,7 @@ Feature: Multiple journals
|
|||
these three eyes
|
||||
these three eyes
|
||||
n
|
||||
Then the output should contain "Encrypted journal 'new_encrypted' created"
|
||||
Then the output should contain "Journal 'new_encrypted' created at "
|
||||
|
||||
Scenario: Don't overwrite main config when encrypting a journal in an alternate config
|
||||
Given the config "basic_onefile.yaml" exists
|
||||
|
|
|
@ -69,7 +69,7 @@ Feature: Reading and writing to journal with custom date formats
|
|||
|
||||
Scenario: Writing an entry at the prompt with custom date
|
||||
Given we use the config "little_endian_dates.yaml"
|
||||
When we run "jrnl" and enter "2013-05-10: I saw Elvis. He's alive."
|
||||
When we run "jrnl" and type "2013-05-10: I saw Elvis. He's alive."
|
||||
Then we should get no error
|
||||
When we run "jrnl -999"
|
||||
Then the output should contain "10.05.2013 09:00 I saw Elvis."
|
||||
|
|
|
@ -2,8 +2,8 @@ Feature: Encrypting and decrypting journals
|
|||
|
||||
Scenario: Decrypting a journal
|
||||
Given we use the config "encrypted.yaml"
|
||||
# And we use the password "bad doggie no biscuit" if prompted
|
||||
When we run "jrnl --decrypt" and enter "bad doggie no biscuit"
|
||||
And we use the password "bad doggie no biscuit" if prompted
|
||||
When we run "jrnl --decrypt"
|
||||
Then the output should contain "Journal decrypted"
|
||||
And the config for journal "default" should contain "encrypt: false"
|
||||
When we run "jrnl -99 --short"
|
||||
|
@ -47,7 +47,7 @@ Feature: Encrypting and decrypting journals
|
|||
Scenario Outline: Running jrnl with encrypt: true on unencryptable journals
|
||||
Given we use the config "<config_file>"
|
||||
When we run "jrnl --config-override encrypt true here is a new entry"
|
||||
Then the error output should contain "this type of journal can't be encrypted"
|
||||
Then the error output should contain "journal can't be encrypted"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
|
|
@ -429,7 +429,7 @@ Feature: Custom formats
|
|||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --export yaml --file nonexistent_dir"
|
||||
Then the output should contain "YAML export must be to individual files"
|
||||
Then the output should contain "YAML export must be to a directory"
|
||||
And the output should not contain "Traceback"
|
||||
|
||||
Examples: configs
|
||||
|
|
|
@ -87,7 +87,7 @@ Feature: Multiple journals
|
|||
these three eyes
|
||||
these three eyes
|
||||
n
|
||||
Then the output should contain "Encrypted journal 'new_encrypted' created"
|
||||
Then the output should contain "Journal 'new_encrypted' created at"
|
||||
|
||||
Scenario: Read and write to journal with emoji name
|
||||
Given we use the config "multiple.yaml"
|
||||
|
|
|
@ -3,7 +3,7 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
|
|||
Scenario: Override configured editor with built-in input === editor:''
|
||||
Given we use the config "basic_encrypted.yaml"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --config-override editor ''" and enter
|
||||
When we run "jrnl --config-override editor ''" and type
|
||||
This is a journal entry
|
||||
Then the stdin prompt should have been called
|
||||
And the editor should not have been called
|
||||
|
|
|
@ -29,7 +29,8 @@ Feature: Starring entries
|
|||
|
||||
Scenario: Starring an entry will mark it in an encrypted journal
|
||||
Given we use the config "encrypted.yaml"
|
||||
When we run "jrnl 20 july 2013 *: Best day of my life!" and enter "bad doggie no biscuit"
|
||||
And we use the password "bad doggie no biscuit" if prompted
|
||||
When we run "jrnl 20 july 2013 *: Best day of my life!"
|
||||
Then the output should contain "Entry added"
|
||||
When we run "jrnl -on 2013-07-20 -starred" and enter "bad doggie no biscuit"
|
||||
Then the output should contain "2013-07-20 09:00 Best day of my life!"
|
||||
|
|
|
@ -41,7 +41,7 @@ Feature: Upgrading Journals from 1.x.x to 2.x.x
|
|||
Scenario: Upgrade with missing journal
|
||||
Given we use the config "upgrade_from_195_with_missing_journal.json"
|
||||
When we run "jrnl --list" and enter "Y"
|
||||
Then the output should contain "Error: features/journals/missing.journal does not exist."
|
||||
Then the output should contain "features/journals/missing.journal does not exist"
|
||||
And we should get no error
|
||||
|
||||
Scenario: Upgrade with missing encrypted journal
|
||||
|
@ -49,6 +49,6 @@ Feature: Upgrading Journals from 1.x.x to 2.x.x
|
|||
When we run "jrnl --list" and enter
|
||||
Y
|
||||
bad doggie no biscuit
|
||||
Then the output should contain "Error: features/journals/missing.journal does not exist."
|
||||
Then the output should contain "features/journals/missing.journal does not exist"
|
||||
And the output should contain "We're all done"
|
||||
And we should get no error
|
||||
|
|
|
@ -172,7 +172,7 @@ Feature: Writing new entries.
|
|||
Scenario Outline: Writing an entry at the prompt (no editor) should store the entry
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "bad doggie no biscuit" if prompted
|
||||
When we run "jrnl" and enter "25 jul 2013: I saw Elvis. He's alive."
|
||||
When we run "jrnl" and type "25 jul 2013: I saw Elvis. He's alive."
|
||||
Then we should get no error
|
||||
When we run "jrnl -on '2013-07-25'"
|
||||
Then the output should contain "2013-07-25 09:00 I saw Elvis."
|
||||
|
@ -233,8 +233,7 @@ Feature: Writing new entries.
|
|||
And we append to the editor if opened
|
||||
[2021-11-13] worked on jrnl tests
|
||||
When we run "jrnl --edit"
|
||||
Then the output should contain
|
||||
[1 entry added]
|
||||
Then the output should contain "1 entry added"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
@ -252,8 +251,7 @@ Feature: Writing new entries.
|
|||
[2021-11-12] worked on jrnl tests again
|
||||
[2021-11-13] worked on jrnl tests a little bit more
|
||||
When we run "jrnl --edit"
|
||||
Then the output should contain
|
||||
[3 entries added]
|
||||
Then the error output should contain "3 entries added"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
@ -269,8 +267,8 @@ Feature: Writing new entries.
|
|||
And we write to the editor if opened
|
||||
[2021-11-13] I am replacing my whole journal with this entry
|
||||
When we run "jrnl --edit"
|
||||
Then the output should contain
|
||||
[2 entries deleted, 1 entry modified]
|
||||
Then the output should contain "2 entries deleted"
|
||||
Then the output should contain "3 entries modified"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
@ -287,7 +285,7 @@ Feature: Writing new entries.
|
|||
[2021-11-13] I am replacing the last entry with this entry
|
||||
When we run "jrnl --edit -1"
|
||||
Then the output should contain
|
||||
[1 entry modified]
|
||||
1 entry modified
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
@ -304,7 +302,7 @@ Feature: Writing new entries.
|
|||
This is a small addendum to my latest entry.
|
||||
When we run "jrnl --edit"
|
||||
Then the output should contain
|
||||
[1 entry modified]
|
||||
1 entry modified
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
|
|
@ -6,12 +6,15 @@ import os
|
|||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
from collections.abc import Iterable
|
||||
from keyring import backend
|
||||
from keyring import errors
|
||||
from pytest import fixture
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import Mock
|
||||
from .helpers import get_fixture
|
||||
import toml
|
||||
from rich.console import Console
|
||||
|
||||
from jrnl.config import load_config
|
||||
from jrnl.os_compat import split_args
|
||||
|
@ -85,7 +88,6 @@ def cli_run(
|
|||
mock_editor,
|
||||
mock_user_input,
|
||||
mock_overrides,
|
||||
mock_password,
|
||||
):
|
||||
# Check if we need more mocks
|
||||
mock_factories.update(mock_args)
|
||||
|
@ -94,7 +96,6 @@ def cli_run(
|
|||
mock_factories.update(mock_editor)
|
||||
mock_factories.update(mock_config_path)
|
||||
mock_factories.update(mock_user_input)
|
||||
mock_factories.update(mock_password)
|
||||
|
||||
return {
|
||||
"status": 0,
|
||||
|
@ -180,26 +181,6 @@ def toml_version(working_dir):
|
|||
return pyproject_contents["tool"]["poetry"]["version"]
|
||||
|
||||
|
||||
@fixture
|
||||
def mock_password(request):
|
||||
def _mock_password():
|
||||
password = get_fixture(request, "password")
|
||||
user_input = get_fixture(request, "user_input")
|
||||
|
||||
if password:
|
||||
password = password.splitlines()
|
||||
|
||||
elif user_input:
|
||||
password = user_input.splitlines()
|
||||
|
||||
if not password:
|
||||
password = Exception("Unexpected call for password")
|
||||
|
||||
return patch("getpass.getpass", side_effect=password)
|
||||
|
||||
return {"getpass": _mock_password}
|
||||
|
||||
|
||||
@fixture
|
||||
def input_method():
|
||||
return ""
|
||||
|
@ -221,30 +202,58 @@ def should_not():
|
|||
|
||||
|
||||
@fixture
|
||||
def mock_user_input(request, is_tty):
|
||||
def _generator(target):
|
||||
def _mock_user_input():
|
||||
user_input = get_fixture(request, "user_input", None)
|
||||
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())
|
||||
|
||||
if user_input is None:
|
||||
user_input = Exception("Unexpected call for user input")
|
||||
else:
|
||||
user_input = user_input.splitlines() if is_tty else [user_input]
|
||||
def mock_console_input(**kwargs):
|
||||
if kwargs["password"] and not isinstance(password_input, Exception):
|
||||
return password_input
|
||||
|
||||
return patch(target, side_effect=user_input)
|
||||
if isinstance(user_input, Iterable):
|
||||
return next(user_input)
|
||||
|
||||
return _mock_user_input
|
||||
# exceptions
|
||||
return user_input if not kwargs["password"] else password_input
|
||||
|
||||
mock_console = Mock(wraps=Console(stderr=True))
|
||||
mock_console.input = Mock(side_effect=mock_console_input)
|
||||
|
||||
return patch("jrnl.output._get_console", return_value=mock_console)
|
||||
|
||||
return {
|
||||
"stdin": _generator("sys.stdin.read"),
|
||||
"input": _generator("builtins.input"),
|
||||
"user_input": _mock_user_input,
|
||||
"stdin_input": lambda: patch("sys.stdin.read", side_effect=stdin_input),
|
||||
}
|
||||
|
||||
|
||||
@fixture
|
||||
def password_input(request):
|
||||
password_input = get_fixture(request, "password", None)
|
||||
if password_input is None:
|
||||
password_input = Exception("Unexpected call for password input")
|
||||
return password_input
|
||||
|
||||
|
||||
@fixture
|
||||
def stdin_input(request, is_tty):
|
||||
stdin_input = get_fixture(request, "all_input", None)
|
||||
if stdin_input is None or is_tty:
|
||||
stdin_input = Exception("Unexpected call for stdin input")
|
||||
else:
|
||||
stdin_input = [stdin_input]
|
||||
return stdin_input
|
||||
|
||||
|
||||
@fixture
|
||||
def is_tty(input_method):
|
||||
assert input_method in ["", "enter", "pipe"]
|
||||
return input_method != "pipe"
|
||||
assert input_method in ["", "enter", "pipe", "type"]
|
||||
return input_method not in ["pipe", "type"]
|
||||
|
||||
|
||||
@fixture
|
||||
|
|
|
@ -120,9 +120,9 @@ def config_exists(config_file, temp_dir, working_dir):
|
|||
shutil.copy2(config_source, config_dest)
|
||||
|
||||
|
||||
@given(parse('we use the password "{pw}" if prompted'), target_fixture="password")
|
||||
def use_password_forever(pw):
|
||||
return pw
|
||||
@given(parse('we use the password "{password}" if prompted'))
|
||||
def use_password_forever(password):
|
||||
return password
|
||||
|
||||
|
||||
@given("we create a cache directory", target_fixture="cache_dir")
|
||||
|
|
|
@ -47,20 +47,23 @@ def output_should_contain(
|
|||
):
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
|
||||
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
|
||||
)
|
||||
), output_str
|
||||
|
||||
elif which_output_stream == "standard":
|
||||
assert (expected_output in cli_run["stdout"]) == we_should
|
||||
assert (expected_output in cli_run["stdout"]) == we_should, output_str
|
||||
|
||||
elif which_output_stream == "error":
|
||||
assert (expected_output in cli_run["stderr"]) == we_should
|
||||
assert (expected_output in cli_run["stderr"]) == we_should, output_str
|
||||
|
||||
else:
|
||||
assert (expected_output in cli_run[which_output_stream]) == we_should
|
||||
assert (
|
||||
expected_output in cli_run[which_output_stream]
|
||||
) == we_should, output_str
|
||||
|
||||
|
||||
@then(parse("the output should not contain\n{expected_output}"))
|
||||
|
@ -164,12 +167,12 @@ def config_var_in_memory(
|
|||
|
||||
@then("we should be prompted for a password")
|
||||
def password_was_called(cli_run):
|
||||
assert cli_run["mocks"]["getpass"].called
|
||||
assert cli_run["mocks"]["user_input"].called
|
||||
|
||||
|
||||
@then("we should not be prompted for a password")
|
||||
def password_was_not_called(cli_run):
|
||||
assert not cli_run["mocks"]["getpass"].called
|
||||
assert not cli_run["mocks"]["user_input"].called
|
||||
|
||||
|
||||
@then(parse("the cache directory should contain the files\n{file_list}"))
|
||||
|
@ -371,7 +374,7 @@ def count_editor_args(num_args, cli_run, editor_state, should_or_should_not):
|
|||
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"].called == we_should
|
||||
assert cli_run["mocks"]["stdin_input"].called == we_should
|
||||
|
||||
|
||||
@then(parse('the editor filename should end with "{suffix}"'))
|
||||
|
|
|
@ -21,12 +21,12 @@ def when_we_change_directory(directory_name):
|
|||
|
||||
# These variables are used in the `@when(re(...))` section below
|
||||
command = '(?P<command>[^"]*)'
|
||||
input_method = "(?P<input_method>enter|pipe)"
|
||||
user_input = '("(?P<user_input>[^"]*)")'
|
||||
input_method = "(?P<input_method>enter|pipe|type)"
|
||||
all_input = '("(?P<all_input>[^"]*)")'
|
||||
|
||||
|
||||
@when(parse('we run "jrnl {command}" and {input_method}\n{user_input}'))
|
||||
@when(re(f'we run "jrnl ?{command}" and {input_method} {user_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('we run "jrnl"')
|
||||
def we_run_jrnl(cli_run, capsys, keyring):
|
||||
|
|
27
tests/unit/test_output.py
Normal file
27
tests/unit/test_output.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Copyright (C) 2012-2021 jrnl contributors
|
||||
# License: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import patch
|
||||
|
||||
from jrnl.messages import Message
|
||||
from jrnl.output import print_msg
|
||||
|
||||
|
||||
@patch("jrnl.output.print_msgs")
|
||||
def test_print_msg_calls_print_msgs_as_list_with_style(print_msgs):
|
||||
test_msg = Mock(Message)
|
||||
print_msg(test_msg)
|
||||
print_msgs.assert_called_once_with([test_msg], style=test_msg.style)
|
||||
|
||||
|
||||
@patch("jrnl.output.print_msgs")
|
||||
def test_print_msg_calls_print_msgs_with_kwargs(print_msgs):
|
||||
test_msg = Mock(Message)
|
||||
kwargs = {
|
||||
"delimter": "test delimiter 🤡",
|
||||
"get_input": True,
|
||||
"hide_input": True,
|
||||
"some_rando_arg": "💩",
|
||||
}
|
||||
print_msg(test_msg, **kwargs)
|
||||
print_msgs.assert_called_once_with([test_msg], style=test_msg.style, **kwargs)
|
Loading…
Add table
Add a link
Reference in a new issue