mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-06-27 21:16:14 +02:00
Implement editor-related steps in pytest-bdd
- Implement mock editor fixture - Add fixture to keep track of editor state - Implement various steps to check editor state Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>
This commit is contained in:
parent
ce64d7973b
commit
3ddfb4d594
6 changed files with 475 additions and 133 deletions
|
@ -1,49 +1,49 @@
|
|||
Feature: Writing new entries.
|
||||
|
||||
Scenario Outline: Multiline entry with punctuation should keep title punctuation
|
||||
Given we use the config "<config_file>.yaml"
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "bad doggie no biscuit" if prompted
|
||||
When we run "jrnl This is. the title\\n This is the second line"
|
||||
And we run "jrnl -n 1"
|
||||
Then the output should contain "This is. the title"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| simple |
|
||||
| empty_folder |
|
||||
| dayone |
|
||||
| encrypted |
|
||||
| config_file |
|
||||
| simple.yaml |
|
||||
| empty_folder.yaml |
|
||||
| dayone.yaml |
|
||||
| encrypted.yaml |
|
||||
|
||||
Scenario Outline: Single line entry with period should be split at period
|
||||
Given we use the config "<config_file>.yaml"
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl This is. the title"
|
||||
And we run "jrnl -1"
|
||||
Then the output should contain "| the title"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| basic_onefile |
|
||||
| basic_encrypted |
|
||||
| basic_folder |
|
||||
| basic_dayone |
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
Scenario Outline: CJK entry should be split at fullwidth period without following space.
|
||||
Given we use the config "<config_file>.yaml"
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl 七転び。八起き"
|
||||
And we run "jrnl -1"
|
||||
Then the output should contain "| 八起き"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| basic_onefile |
|
||||
| basic_encrypted |
|
||||
| basic_folder |
|
||||
| basic_dayone |
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
Scenario Outline: Writing an entry from command line should store the entry
|
||||
Given we use the config "<config_file>.yaml"
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "bad doggie no biscuit" if prompted
|
||||
When we run "jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa."
|
||||
Then we should see the message "Entry added"
|
||||
|
@ -51,49 +51,47 @@ Feature: Writing new entries.
|
|||
Then the output should contain "2013-07-23 09:00 A cold and stormy day."
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| simple |
|
||||
| empty_folder |
|
||||
| dayone |
|
||||
| encrypted |
|
||||
| config_file |
|
||||
| simple.yaml |
|
||||
| empty_folder.yaml |
|
||||
| dayone.yaml |
|
||||
| encrypted.yaml |
|
||||
|
||||
Scenario Outline: Writing a partial entry from command line with edit flag should go to the editor
|
||||
Given we use the config "<config_file>.yaml"
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl this is a partial --edit"
|
||||
Then we should see the message "Entry added"
|
||||
Then the editor should have been called
|
||||
And the editor file content should be
|
||||
"""
|
||||
this is a partial
|
||||
"""
|
||||
When we run "jrnl -n 1"
|
||||
Then the output should contain "this is a partial"
|
||||
this is a partial
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| basic_onefile |
|
||||
| basic_encrypted |
|
||||
| basic_dayone |
|
||||
| basic_folder |
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_dayone.yaml |
|
||||
| basic_folder.yaml |
|
||||
|
||||
Scenario Outline: Writing an empty entry from the editor should yield "Nothing saved to file" message
|
||||
Given we use the config "<config_file>.yaml"
|
||||
Given we use the config "<config_file>"
|
||||
And we write nothing to the editor if opened
|
||||
And we use the password "test" if prompted
|
||||
When we open the editor and enter nothing
|
||||
When we run "jrnl --edit"
|
||||
Then the error output should contain "[Nothing saved to file]"
|
||||
And the editor should have been called
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| editor |
|
||||
| editor_empty_folder |
|
||||
| dayone |
|
||||
| basic_encrypted |
|
||||
| basic_onefile |
|
||||
| config_file |
|
||||
| editor.yaml |
|
||||
| editor_empty_folder.yaml |
|
||||
| dayone.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_onefile.yaml |
|
||||
|
||||
@skip
|
||||
Scenario Outline: Writing an empty entry from the command line with no editor should yield nothing
|
||||
Given we use the config "<config_file>.yaml"
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "bad doggie no biscuit" if prompted
|
||||
When we run "jrnl" and enter nothing
|
||||
Then the output should be empty
|
||||
|
@ -101,15 +99,15 @@ Feature: Writing new entries.
|
|||
And the editor should not have been called
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| simple |
|
||||
| empty_folder |
|
||||
| encrypted |
|
||||
# | dayone | @todo
|
||||
| config_file |
|
||||
| config_simple.yaml |
|
||||
| empty_folder.yaml |
|
||||
| encrypted.yaml |
|
||||
# | dayone | @todo
|
||||
|
||||
Scenario Outline: Writing an entry does not print the entire journal
|
||||
# https://github.com/jrnl-org/jrnl/issues/87
|
||||
Given we use the config "<config_file>.yaml"
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "bad doggie no biscuit" if prompted
|
||||
When we run "jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa."
|
||||
Then we should see the message "Entry added"
|
||||
|
@ -117,33 +115,31 @@ Feature: Writing new entries.
|
|||
Then the output should not contain "Life is good"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| editor |
|
||||
| editor_empty_folder |
|
||||
| dayone |
|
||||
| encrypted |
|
||||
| config_file |
|
||||
| editor.yaml |
|
||||
| editor_empty_folder.yaml |
|
||||
| dayone.yaml |
|
||||
| encrypted.yaml |
|
||||
|
||||
Scenario Outline: Embedded period stays in title
|
||||
Given we use the config "<config_file>.yaml"
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "bad doggie no biscuit" if prompted
|
||||
When we run "jrnl 04-24-2014: Created a new website - empty.com. Hope to get a lot of traffic."
|
||||
Then we should see the message "Entry added"
|
||||
When we run "jrnl -1"
|
||||
Then the output should be
|
||||
"""
|
||||
2014-04-24 09:00 Created a new website - empty.com.
|
||||
| Hope to get a lot of traffic.
|
||||
"""
|
||||
2014-04-24 09:00 Created a new website - empty.com.
|
||||
| Hope to get a lot of traffic.
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| simple |
|
||||
| empty_folder |
|
||||
| dayone |
|
||||
| encrypted |
|
||||
| config_file |
|
||||
| simple.yaml |
|
||||
| empty_folder.yaml |
|
||||
| dayone.yaml |
|
||||
| encrypted.yaml |
|
||||
|
||||
Scenario Outline: Write and read emoji support
|
||||
Given we use the config "<config_file>.yaml"
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "bad doggie no biscuit" if prompted
|
||||
When we run "jrnl 23 july 2013: 🌞 sunny day. Saw an 🐘"
|
||||
Then we should see the message "Entry added"
|
||||
|
@ -152,14 +148,14 @@ Feature: Writing new entries.
|
|||
And the output should contain "🐘"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| simple |
|
||||
| empty_folder |
|
||||
| dayone |
|
||||
| encrypted |
|
||||
| config_file |
|
||||
| simple.yaml |
|
||||
| empty_folder.yaml |
|
||||
| dayone.yaml |
|
||||
| encrypted.yaml |
|
||||
|
||||
Scenario Outline: Writing an entry at the prompt (no editor) should store the entry
|
||||
Given we use the config "<config_file>.yaml"
|
||||
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."
|
||||
Then we should get no error
|
||||
|
@ -168,10 +164,10 @@ Feature: Writing new entries.
|
|||
And the output should contain "| He's alive."
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| simple |
|
||||
| empty_folder |
|
||||
| encrypted |
|
||||
| config_file |
|
||||
| simple.yaml |
|
||||
| empty_folder.yaml |
|
||||
| encrypted.yaml |
|
||||
|
||||
@todo
|
||||
Scenario: Writing an entry at the prompt (no editor) in DayOne journal
|
||||
|
@ -187,26 +183,29 @@ Feature: Writing new entries.
|
|||
Given we use the config "dayone.yaml"
|
||||
When we run "jrnl 01 may 1979: Being born hurts."
|
||||
And we run "jrnl --export json"
|
||||
Then "entries" in the json output should have 5 elements
|
||||
And the json output should contain entries.0.creator.software_agent
|
||||
And the json output should contain entries.0.creator.os_agent
|
||||
And the json output should contain entries.0.creator.host_name
|
||||
And the json output should contain entries.0.creator.generation_date
|
||||
And the json output should contain entries.0.creator.device_agent
|
||||
And "entries.0.creator.software_agent" in the json output should contain "jrnl"
|
||||
Then we should get no error
|
||||
And the output should be valid JSON
|
||||
Given we parse the output as JSON
|
||||
Then "entries" in the parsed output should have 5 elements
|
||||
And "entries.0.creator" in the parsed output should be
|
||||
software_agent
|
||||
os_agent
|
||||
host_name
|
||||
generation_date
|
||||
device_agent
|
||||
And "entries.0.creator.software_agent" in the parsed output should contain
|
||||
jrnl
|
||||
|
||||
# fails when system time is UTC (as on Travis-CI)
|
||||
@skip
|
||||
# @skip
|
||||
Scenario: Title with an embedded period on DayOne journal
|
||||
Given we use the config "dayone.yaml"
|
||||
When we run "jrnl 04-24-2014: "Ran 6.2 miles today in 1:02:03. I'm feeling sore because I forgot to stretch.""
|
||||
Then we should see the message "Entry added"
|
||||
When we run "jrnl -1"
|
||||
Then the output should be
|
||||
"""
|
||||
2014-04-24 09:00 Ran 6.2 miles today in 1:02:03.
|
||||
| I'm feeling sore because I forgot to stretch.
|
||||
"""
|
||||
2014-04-24 09:00 Ran 6.2 miles today in 1:02:03.
|
||||
| I'm feeling sore because I forgot to stretch.
|
||||
|
||||
Scenario: Opening an folder that's not a DayOne folder should treat as folder journal
|
||||
Given we use the config "empty_folder.yaml"
|
||||
|
|
|
@ -8,6 +8,7 @@ from collections import defaultdict
|
|||
from keyring import backend
|
||||
from keyring import set_keyring
|
||||
from keyring import errors
|
||||
from pathlib import Path
|
||||
import random
|
||||
import string
|
||||
import re
|
||||
|
@ -191,12 +192,88 @@ def which_output_stream():
|
|||
return None
|
||||
|
||||
|
||||
@fixture
|
||||
def editor_input():
|
||||
return None
|
||||
|
||||
|
||||
@fixture
|
||||
def num_args():
|
||||
return None
|
||||
|
||||
|
||||
@fixture
|
||||
def parsed_output():
|
||||
return {"lang": None, "obj": None}
|
||||
|
||||
|
||||
@fixture
|
||||
def editor_state():
|
||||
return {
|
||||
"command": "",
|
||||
"intent": {"method": "r", "input": None},
|
||||
"tmpfile": {"name": None, "content": None},
|
||||
}
|
||||
|
||||
|
||||
@fixture
|
||||
def editor(editor_state):
|
||||
def _mock_editor(editor_command):
|
||||
tmpfile = editor_command[-1]
|
||||
|
||||
editor_state["command"] = editor_command
|
||||
editor_state["tmpfile"]["name"] = tmpfile
|
||||
|
||||
Path(tmpfile).touch()
|
||||
with open(tmpfile, editor_state["intent"]["method"]) as f:
|
||||
# Touch the file so jrnl knows it was edited
|
||||
if editor_state["intent"]["input"] != None:
|
||||
f.write(editor_state["intent"]["input"])
|
||||
|
||||
file_content = f.read()
|
||||
editor_state["tmpfile"]["content"] = file_content
|
||||
|
||||
return _mock_editor
|
||||
|
||||
|
||||
# ----- STEPS ----- #
|
||||
@given(parse("we {editor_method} to the editor if opened\n{editor_input}"))
|
||||
@given(parse("we {editor_method} nothing to the editor if opened"))
|
||||
def we_enter_editor(editor_method, editor_input, editor_state):
|
||||
file_method = editor_state["intent"]["method"]
|
||||
if editor_method == "write":
|
||||
file_method = "w+"
|
||||
elif editor_method == "append":
|
||||
file_method = "a+"
|
||||
else:
|
||||
assert False, f"Method '{editor_method}' not supported"
|
||||
|
||||
editor_state["intent"] = {"method": file_method, "input": editor_input}
|
||||
|
||||
|
||||
@then(parse("the editor should have been called"))
|
||||
@then(parse("the editor should have been called with {num_args} arguments"))
|
||||
def count_editor_args(num_args, cli_run, editor_state):
|
||||
assert cli_run["mocks"]["editor"].called
|
||||
|
||||
if isinstance(num_args, int):
|
||||
assert len(editor_state["command"]) == int(num_args)
|
||||
|
||||
|
||||
@then(parse('the editor file content should {comparison} "{str_value}"'))
|
||||
@then(parse("the editor file content should {comparison} empty"))
|
||||
@then(parse("the editor file content should {comparison}\n{str_value}"))
|
||||
def contains_editor_file(comparison, str_value, editor_state):
|
||||
content = editor_state["tmpfile"]["content"]
|
||||
# content = f'\n"""\n{content}\n"""\n'
|
||||
if comparison == "be":
|
||||
assert content == str_value
|
||||
elif comparison == "contain":
|
||||
assert str_value in content
|
||||
else:
|
||||
assert False, f"Comparison '{comparison}' not supported"
|
||||
|
||||
|
||||
@given("we have a keyring", target_fixture="keyring")
|
||||
@given(parse("we have a {keyring_type} keyring"), target_fixture="keyring")
|
||||
def we_have_type_of_keyring(keyring_type):
|
||||
|
@ -245,7 +322,15 @@ def use_password_forever(pw):
|
|||
@when('we run "jrnl <command>"')
|
||||
@when('we run "jrnl"')
|
||||
def we_run(
|
||||
command, config_path, user_input, cli_run, capsys, password, keyring, cache_dir
|
||||
command,
|
||||
config_path,
|
||||
user_input,
|
||||
cli_run,
|
||||
capsys,
|
||||
password,
|
||||
keyring,
|
||||
cache_dir,
|
||||
editor,
|
||||
):
|
||||
if cache_dir["exists"]:
|
||||
command = command.format(cache_dir=cache_dir["path"])
|
||||
|
@ -270,7 +355,8 @@ def we_run(
|
|||
patch("builtins.input", side_effect=user_input) as mock_input, \
|
||||
patch("getpass.getpass", side_effect=password) as mock_getpass, \
|
||||
patch("jrnl.install.get_config_path", return_value=config_path), \
|
||||
patch("jrnl.config.get_config_path", return_value=config_path) \
|
||||
patch("jrnl.config.get_config_path", return_value=config_path), \
|
||||
patch("subprocess.call", side_effect=editor) as mock_editor \
|
||||
: # @TODO: single point of truth for get_config_path (move from all calls from install to config)
|
||||
try:
|
||||
cli(args)
|
||||
|
@ -290,6 +376,7 @@ def we_run(
|
|||
"stdin": mock_stdin,
|
||||
"input": mock_input,
|
||||
"getpass": mock_getpass,
|
||||
"editor": mock_editor,
|
||||
}
|
||||
|
||||
|
||||
|
@ -526,11 +613,15 @@ def assert_parsed_output_item_count(node_name, number, parsed_output):
|
|||
assert False, f"Language name {lang} not recognized"
|
||||
|
||||
|
||||
@then(parse('"{field_name}" in the parsed output should be\n{expected_keys}'))
|
||||
def assert_output_field_content(field_name, expected_keys, cli_run, parsed_output):
|
||||
@then(parse('"{field_name}" in the parsed output should {comparison}\n{expected_keys}'))
|
||||
def assert_output_field_content(
|
||||
field_name, comparison, expected_keys, cli_run, parsed_output
|
||||
):
|
||||
lang = parsed_output["lang"]
|
||||
obj = parsed_output["obj"]
|
||||
expected_keys = expected_keys.split("\n")
|
||||
if len(expected_keys) == 1:
|
||||
expected_keys = expected_keys[0]
|
||||
|
||||
if lang == "XML":
|
||||
xml_node_names = (node.tag for node in obj)
|
||||
|
@ -555,10 +646,22 @@ def assert_output_field_content(field_name, expected_keys, cli_run, parsed_outpu
|
|||
assert node in my_obj, [my_obj.keys(), node]
|
||||
my_obj = my_obj[node]
|
||||
|
||||
if type(my_obj) is str:
|
||||
my_obj = [my_obj]
|
||||
|
||||
assert set(expected_keys) == set(my_obj), [set(my_obj), set(expected_keys)]
|
||||
if comparison == "be":
|
||||
if type(my_obj) is str:
|
||||
assert expected_keys == my_obj, [my_obj, expected_keys]
|
||||
else:
|
||||
assert set(expected_keys) == set(my_obj), [
|
||||
set(my_obj),
|
||||
set(expected_keys),
|
||||
]
|
||||
elif comparison == "contain":
|
||||
if type(my_obj) is str:
|
||||
assert expected_keys in my_obj, [my_obj, expected_keys]
|
||||
else:
|
||||
assert all(elem in my_obj for elem in expected_keys), [
|
||||
my_obj,
|
||||
expected_keys,
|
||||
]
|
||||
else:
|
||||
assert False, f"Language name {lang} not recognized"
|
||||
|
||||
|
|
|
@ -5,13 +5,13 @@ scenarios("../features/core.feature")
|
|||
scenarios("../features/datetime.feature")
|
||||
scenarios("../features/delete.feature")
|
||||
scenarios("../features/encrypt.feature")
|
||||
# scenarios("../features/file_storage.feature")
|
||||
scenarios("../features/file_storage.feature")
|
||||
scenarios("../features/format.feature")
|
||||
# scenarios("../features/import.feature")
|
||||
# scenarios("../features/multiple_journals.feature")
|
||||
scenarios("../features/import.feature")
|
||||
scenarios("../features/multiple_journals.feature")
|
||||
scenarios("../features/password.feature")
|
||||
# scenarios("../features/search.feature")
|
||||
# scenarios("../features/star.feature")
|
||||
# scenarios("../features/tag.feature")
|
||||
# scenarios("../features/upgrade.feature")
|
||||
# scenarios("../features/write.feature")
|
||||
scenarios("../features/search.feature")
|
||||
scenarios("../features/star.feature")
|
||||
scenarios("../features/tag.feature")
|
||||
scenarios("../features/upgrade.feature")
|
||||
scenarios("../features/write.feature")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue